@j0hanz/filesystem-mcp 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/schemas.d.ts CHANGED
@@ -403,6 +403,7 @@ export declare const EditFileOutputSchema: z.ZodObject<{
403
403
  ok: z.ZodBoolean;
404
404
  path: z.ZodOptional<z.ZodString>;
405
405
  appliedEdits: z.ZodOptional<z.ZodNumber>;
406
+ lineRange: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
406
407
  unmatchedEdits: z.ZodOptional<z.ZodArray<z.ZodString>>;
407
408
  error: z.ZodOptional<z.ZodObject<{
408
409
  code: z.ZodEnum<{
package/dist/schemas.js CHANGED
@@ -510,6 +510,10 @@ export const EditFileOutputSchema = z.object({
510
510
  ok: z.boolean(),
511
511
  path: z.string().optional(),
512
512
  appliedEdits: z.number().optional(),
513
+ lineRange: z
514
+ .tuple([z.number(), z.number()])
515
+ .optional()
516
+ .describe('Line range modified [start, end] (1-based)'),
513
517
  unmatchedEdits: z
514
518
  .array(z.string())
515
519
  .optional()
@@ -21,25 +21,45 @@ function applyEdits(content, edits) {
21
21
  let newContent = content;
22
22
  let appliedEdits = 0;
23
23
  const unmatchedEdits = [];
24
+ let minLine;
25
+ let maxLine;
24
26
  for (const edit of edits) {
25
27
  if (!newContent.includes(edit.oldText)) {
26
28
  unmatchedEdits.push(edit.oldText);
27
29
  continue;
28
30
  }
31
+ const index = newContent.indexOf(edit.oldText);
32
+ const linesBefore = newContent.slice(0, index).split('\n').length;
33
+ const newTextLines = edit.newText.split('\n').length;
34
+ const startLine = linesBefore;
35
+ const endLine = linesBefore + newTextLines - 1;
36
+ if (minLine === undefined || startLine < minLine)
37
+ minLine = startLine;
38
+ if (maxLine === undefined || endLine > maxLine)
39
+ maxLine = endLine;
29
40
  newContent = newContent.replace(edit.oldText, edit.newText);
30
41
  appliedEdits += 1;
31
42
  }
32
- return { content: newContent, appliedEdits, unmatchedEdits };
43
+ const result = {
44
+ content: newContent,
45
+ appliedEdits,
46
+ unmatchedEdits,
47
+ };
48
+ if (minLine !== undefined && maxLine !== undefined) {
49
+ result.lineRange = [minLine, maxLine];
50
+ }
51
+ return result;
33
52
  }
34
53
  async function handleEditFile(args, signal) {
35
54
  const validPath = await validateExistingPath(args.path, signal);
36
55
  const content = await fs.readFile(validPath, { encoding: 'utf-8', signal });
37
- const { content: newContent, appliedEdits, unmatchedEdits, } = applyEdits(content, args.edits);
56
+ const { content: newContent, appliedEdits, unmatchedEdits, lineRange, } = applyEdits(content, args.edits);
38
57
  const structured = {
39
58
  ok: true,
40
59
  path: validPath,
41
60
  appliedEdits,
42
61
  ...(unmatchedEdits.length > 0 ? { unmatchedEdits } : {}),
62
+ ...(lineRange ? { lineRange } : {}),
43
63
  };
44
64
  if (args.dryRun) {
45
65
  return buildToolResponse(`Dry run complete. ${appliedEdits} edits would be applied.`, structured);
@@ -68,5 +88,17 @@ export function registerEditFileTool(server, options = {}) {
68
88
  const name = path.basename(args.path);
69
89
  return `🛠 edit: ${name} (${args.edits.length} edits)`;
70
90
  },
91
+ completionMessage: (args, result) => {
92
+ const name = path.basename(args.path);
93
+ if (result.isError)
94
+ return `🛠 edit: ${name} ➟ Failed`;
95
+ const sc = result.structuredContent;
96
+ if (!sc.ok)
97
+ return `🛠 edit: ${name} ➟ Failed`;
98
+ if (sc.lineRange) {
99
+ return `🛠 edit: ${name} ➟ [${sc.lineRange[0]}-${sc.lineRange[1]}]`;
100
+ }
101
+ return `🛠 edit: ${name} ➟ (${sc.appliedEdits ?? 0} edits)`;
102
+ },
71
103
  }));
72
104
  }
@@ -202,7 +202,17 @@ export function registerSearchContentTool(server, options = {}) {
202
202
  });
203
203
  const result = await handleSearchContent(normalizedArgs, extra.signal, options.resourceStore, createProgressReporter(extra));
204
204
  const sc = result.structuredContent;
205
- const suffix = sc.ok && sc.totalMatches ? String(sc.totalMatches) : 'No matches';
205
+ const count = sc.ok && sc.totalMatches ? sc.totalMatches : 0;
206
+ let suffix;
207
+ if (count === 0) {
208
+ suffix = 'No matches';
209
+ }
210
+ else if (count === 1) {
211
+ suffix = '1 match';
212
+ }
213
+ else {
214
+ suffix = `${count} matches`;
215
+ }
206
216
  const finalCurrent = (sc.filesScanned ?? 0) + 1;
207
217
  notifyProgress(extra, {
208
218
  current: finalCurrent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/filesystem-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "mcpName": "io.github.j0hanz/filesystem-mcp",
5
5
  "description": "MCP Server that enables LLMs to interact with the local filesystem.",
6
6
  "type": "module",