@j0hanz/filesystem-mcp 1.16.2 → 1.16.3

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 (69) hide show
  1. package/README.md +18 -14
  2. package/dist/assets/logo.svg +26 -11
  3. package/dist/cli.js +1 -1
  4. package/dist/completions.d.ts +1 -1
  5. package/dist/completions.js +1 -2
  6. package/dist/lib/constants.d.ts +2 -0
  7. package/dist/lib/constants.js +2 -3
  8. package/dist/lib/fs-helpers.d.ts +1 -1
  9. package/dist/lib/fs-helpers.js +0 -1
  10. package/dist/lib/logger.d.ts +3 -4
  11. package/dist/lib/logger.js +1 -1
  12. package/dist/lib/paths.d.ts +1 -1
  13. package/dist/prompts.d.ts +1 -1
  14. package/dist/prompts.js +11 -11
  15. package/dist/resources/workflows.js +12 -0
  16. package/dist/resources.d.ts +1 -1
  17. package/dist/resources.js +1 -1
  18. package/dist/schemas.d.ts +4 -4
  19. package/dist/server/bootstrap.d.ts +1 -10
  20. package/dist/server/bootstrap.js +165 -95
  21. package/dist/server/roots-manager.d.ts +3 -2
  22. package/dist/server/roots-manager.js +30 -4
  23. package/dist/server/task-store.d.ts +2 -3
  24. package/dist/server/task-store.js +1 -1
  25. package/dist/tools/apply-patch.d.ts +1 -1
  26. package/dist/tools/apply-patch.js +16 -12
  27. package/dist/tools/calculate-hash.d.ts +1 -1
  28. package/dist/tools/calculate-hash.js +8 -12
  29. package/dist/tools/contract.d.ts +5 -0
  30. package/dist/tools/create-directory.d.ts +1 -1
  31. package/dist/tools/create-directory.js +7 -10
  32. package/dist/tools/delete-file.d.ts +1 -1
  33. package/dist/tools/delete-file.js +12 -11
  34. package/dist/tools/diff-files.d.ts +1 -1
  35. package/dist/tools/diff-files.js +8 -11
  36. package/dist/tools/edit-file.d.ts +1 -1
  37. package/dist/tools/edit-file.js +12 -11
  38. package/dist/tools/icons.d.ts +15 -0
  39. package/dist/tools/icons.js +24 -0
  40. package/dist/tools/list-directory.d.ts +1 -1
  41. package/dist/tools/list-directory.js +7 -10
  42. package/dist/tools/move-file.d.ts +1 -1
  43. package/dist/tools/move-file.js +12 -11
  44. package/dist/tools/read-multiple.d.ts +1 -1
  45. package/dist/tools/read-multiple.js +8 -12
  46. package/dist/tools/read.d.ts +1 -1
  47. package/dist/tools/read.js +7 -10
  48. package/dist/tools/replace-in-files.d.ts +1 -1
  49. package/dist/tools/replace-in-files.js +11 -13
  50. package/dist/tools/roots.d.ts +1 -1
  51. package/dist/tools/roots.js +7 -10
  52. package/dist/tools/search-content.d.ts +1 -1
  53. package/dist/tools/search-content.js +8 -13
  54. package/dist/tools/search-files.d.ts +1 -1
  55. package/dist/tools/search-files.js +11 -16
  56. package/dist/tools/shared.d.ts +15 -12
  57. package/dist/tools/shared.js +84 -56
  58. package/dist/tools/stat-many.d.ts +1 -1
  59. package/dist/tools/stat-many.js +8 -12
  60. package/dist/tools/stat.d.ts +1 -1
  61. package/dist/tools/stat.js +7 -10
  62. package/dist/tools/task-support.d.ts +14 -12
  63. package/dist/tools/task-support.js +98 -92
  64. package/dist/tools/tree.d.ts +1 -1
  65. package/dist/tools/tree.js +11 -15
  66. package/dist/tools/write-file.d.ts +1 -1
  67. package/dist/tools/write-file.js +12 -11
  68. package/dist/tools.d.ts +1 -1
  69. package/package.json +6 -4
@@ -4,8 +4,9 @@ import { withAbort } from '../lib/abort.js';
4
4
  import { ErrorCode, McpError } from '../lib/errors.js';
5
5
  import { validatePathForWrite } from '../lib/paths.js';
6
6
  import { CreateDirectoryInputSchema, CreateDirectoryOutputSchema, } from '../schemas.js';
7
- import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, IDEMPOTENT_WRITE_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
8
- import { registerToolTaskIfAvailable } from './task-support.js';
7
+ import { DIR_CREATE_ICONS } from './icons.js';
8
+ import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, IDEMPOTENT_WRITE_TOOL_ANNOTATIONS, } from './shared.js';
9
+ import { registerStandardTool } from './task-support.js';
9
10
  export const CREATE_DIRECTORY_TOOL = {
10
11
  name: 'mkdir',
11
12
  title: 'Create Directory',
@@ -13,6 +14,7 @@ export const CREATE_DIRECTORY_TOOL = {
13
14
  inputSchema: CreateDirectoryInputSchema,
14
15
  outputSchema: CreateDirectoryOutputSchema,
15
16
  annotations: IDEMPOTENT_WRITE_TOOL_ANNOTATIONS,
17
+ icons: DIR_CREATE_ICONS,
16
18
  taskSupport: 'forbidden',
17
19
  };
18
20
  async function handleCreateDirectory(args, signal) {
@@ -34,17 +36,16 @@ async function handleCreateDirectory(args, signal) {
34
36
  });
35
37
  }
36
38
  export function registerCreateDirectoryTool(server, options = {}) {
37
- const handler = (args, extra) => executeToolWithDiagnostics({
39
+ const handler = (args, ctx) => executeToolWithDiagnostics({
38
40
  toolName: 'mkdir',
39
- extra,
41
+ ctx,
40
42
  outputSchema: CreateDirectoryOutputSchema,
41
43
  timedSignal: {},
42
44
  context: { path: args.path ?? args.paths?.[0] },
43
45
  run: (signal) => handleCreateDirectory(args, signal),
44
46
  onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path ?? args.paths?.[0]),
45
47
  });
46
- const wrappedHandler = wrapToolHandler(handler, {
47
- guard: options.isInitialized,
48
+ registerStandardTool(server, CREATE_DIRECTORY_TOOL, handler, options, {
48
49
  progressMessage: (args) => {
49
50
  if (args.path && !args.paths?.length) {
50
51
  return `🛠 mkdir: ${basename(args.path)}`;
@@ -65,8 +66,4 @@ export function registerCreateDirectoryTool(server, options = {}) {
65
66
  return `🛠 mkdir: ${count} directories`;
66
67
  },
67
68
  });
68
- const validatedHandler = withValidatedArgs(CreateDirectoryInputSchema, wrappedHandler);
69
- if (registerToolTaskIfAvailable(server, 'mkdir', CREATE_DIRECTORY_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
70
- return;
71
- server.registerTool('mkdir', withDefaultIcons({ ...CREATE_DIRECTORY_TOOL }, options.iconInfo), validatedHandler);
72
69
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const DELETE_FILE_TOOL: ToolContract;
4
4
  export declare function registerDeleteFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -5,8 +5,9 @@ import { ErrorCode, isNodeError, McpError } from '../lib/errors.js';
5
5
  import { Logger } from '../lib/logger.js';
6
6
  import { isAllowedDirectoryRoot, validatePathForWrite } from '../lib/paths.js';
7
7
  import { DeleteFileInputSchema, DeleteFileOutputSchema } from '../schemas.js';
8
- import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
9
- import { registerToolTaskIfAvailable } from './task-support.js';
8
+ import { FILE_DELETE_ICONS } from './icons.js';
9
+ import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, } from './shared.js';
10
+ import { registerStandardTool } from './task-support.js';
10
11
  export const DELETE_FILE_TOOL = {
11
12
  name: 'rm',
12
13
  title: 'Delete File',
@@ -14,6 +15,7 @@ export const DELETE_FILE_TOOL = {
14
15
  inputSchema: DeleteFileInputSchema,
15
16
  outputSchema: DeleteFileOutputSchema,
16
17
  annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
18
+ icons: FILE_DELETE_ICONS,
17
19
  gotchas: ['Non-empty directories require `recursive=true`.'],
18
20
  taskSupport: 'forbidden',
19
21
  };
@@ -55,13 +57,17 @@ async function handleDeleteFile(args, signal) {
55
57
  });
56
58
  }
57
59
  export function registerDeleteFileTool(server, options = {}) {
58
- const handler = (args, extra) => executeToolWithDiagnostics({
60
+ const handler = (args, ctx) => executeToolWithDiagnostics({
59
61
  toolName: 'rm',
60
- extra,
62
+ ctx,
61
63
  outputSchema: DeleteFileOutputSchema,
62
64
  timedSignal: {},
63
65
  context: { path: args.path },
64
- run: (signal) => handleDeleteFile(args, signal),
66
+ run: async (signal) => {
67
+ const result = await handleDeleteFile(args, signal);
68
+ void ctx.log?.('info', `rm: ${args.path}`);
69
+ return result;
70
+ },
65
71
  onError: (error) => {
66
72
  if (isNodeError(error)) {
67
73
  if (error.code === 'ENOENT') {
@@ -83,8 +89,7 @@ export function registerDeleteFileTool(server, options = {}) {
83
89
  return buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path);
84
90
  },
85
91
  });
86
- const wrappedHandler = wrapToolHandler(handler, {
87
- guard: options.isInitialized,
92
+ registerStandardTool(server, DELETE_FILE_TOOL, handler, options, {
88
93
  progressMessage: (args) => `🛠 rm: ${basename(args.path)}`,
89
94
  completionMessage: (args, result) => {
90
95
  const name = basename(args.path);
@@ -93,8 +98,4 @@ export function registerDeleteFileTool(server, options = {}) {
93
98
  return `🛠 rm: ${name}`;
94
99
  },
95
100
  });
96
- const validatedHandler = withValidatedArgs(DeleteFileInputSchema, wrappedHandler);
97
- if (registerToolTaskIfAvailable(server, 'rm', DELETE_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
98
- return;
99
- server.registerTool('rm', withDefaultIcons({ ...DELETE_FILE_TOOL }, options.iconInfo), validatedHandler);
100
101
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const DIFF_FILES_TOOL: ToolContract;
4
4
  export declare function registerDiffFilesTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -6,8 +6,9 @@ import { MAX_TEXT_FILE_SIZE } from '../lib/constants.js';
6
6
  import { ErrorCode, McpError } from '../lib/errors.js';
7
7
  import { validateExistingPath } from '../lib/paths.js';
8
8
  import { DiffFilesInputSchema, DiffFilesOutputSchema } from '../schemas.js';
9
- import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
10
- import { registerToolTaskIfAvailable } from './task-support.js';
9
+ import { FILE_READ_ICONS } from './icons.js';
10
+ import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, } from './shared.js';
11
+ import { registerStandardTool } from './task-support.js';
11
12
  export const DIFF_FILES_TOOL = {
12
13
  name: 'diff_files',
13
14
  title: 'Diff Files',
@@ -17,7 +18,8 @@ export const DIFF_FILES_TOOL = {
17
18
  inputSchema: DiffFilesInputSchema,
18
19
  outputSchema: DiffFilesOutputSchema,
19
20
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
20
- taskSupport: 'forbidden',
21
+ icons: FILE_READ_ICONS,
22
+ taskSupport: 'optional',
21
23
  };
22
24
  function computeDiffStats(hunks) {
23
25
  let linesAdded = 0;
@@ -101,17 +103,16 @@ async function handleDiffFiles(args, signal, resourceStore) {
101
103
  ]);
102
104
  }
103
105
  export function registerDiffFilesTool(server, options = {}) {
104
- const handler = (args, extra) => executeToolWithDiagnostics({
106
+ const handler = (args, ctx) => executeToolWithDiagnostics({
105
107
  toolName: 'diff_files',
106
- extra,
108
+ ctx,
107
109
  outputSchema: DiffFilesOutputSchema,
108
110
  timedSignal: {},
109
111
  context: { path: args.original },
110
112
  run: (signal) => handleDiffFiles(args, signal, options.resourceStore),
111
113
  onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.original),
112
114
  });
113
- const wrappedHandler = wrapToolHandler(handler, {
114
- guard: options.isInitialized,
115
+ registerStandardTool(server, DIFF_FILES_TOOL, handler, options, {
115
116
  progressMessage: (args) => {
116
117
  const n1 = basename(args.original);
117
118
  const n2 = basename(args.modified);
@@ -132,8 +133,4 @@ export function registerDiffFilesTool(server, options = {}) {
132
133
  return `🕮 diff: ${n1} ⟷ ${n2}`;
133
134
  },
134
135
  });
135
- const validatedHandler = withValidatedArgs(DiffFilesInputSchema, wrappedHandler);
136
- if (registerToolTaskIfAvailable(server, 'diff_files', DIFF_FILES_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
137
- return;
138
- server.registerTool('diff_files', withDefaultIcons({ ...DIFF_FILES_TOOL }, options.iconInfo), validatedHandler);
139
136
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const EDIT_FILE_TOOL: ToolContract;
4
4
  export declare function registerEditFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -9,8 +9,9 @@ import { atomicWriteFile } from '../lib/fs-helpers.js';
9
9
  import { Logger } from '../lib/logger.js';
10
10
  import { assertAllowedFileAccess, validateExistingPath } from '../lib/paths.js';
11
11
  import { EditFileInputSchema, EditFileOutputSchema } from '../schemas.js';
12
- import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
13
- import { registerToolTaskIfAvailable } from './task-support.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';
14
15
  export const EDIT_FILE_TOOL = {
15
16
  name: 'edit',
16
17
  title: 'Edit File',
@@ -20,6 +21,7 @@ export const EDIT_FILE_TOOL = {
20
21
  inputSchema: EditFileInputSchema,
21
22
  outputSchema: EditFileOutputSchema,
22
23
  annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
24
+ icons: FILE_EDIT_ICONS,
23
25
  nuances: ['Each edit applies to the output of the previous edit.'],
24
26
  taskSupport: 'forbidden',
25
27
  };
@@ -229,22 +231,21 @@ async function handleEditFile(args, signal) {
229
231
  return buildToolResponse(buildEditMessage(args.path, editResult), structured);
230
232
  }
231
233
  export function registerEditFileTool(server, options = {}) {
232
- const handler = (args, extra) => executeToolWithDiagnostics({
234
+ const handler = (args, ctx) => executeToolWithDiagnostics({
233
235
  toolName: 'edit',
234
- extra,
236
+ ctx,
235
237
  outputSchema: EditFileOutputSchema,
236
238
  timedSignal: {},
237
239
  context: { path: args.path },
238
- run: (signal) => handleEditFile(args, signal),
240
+ run: async (signal) => {
241
+ const result = await handleEditFile(args, signal);
242
+ void ctx.log?.('info', `edit: ${args.path} (${String(result.structuredContent.appliedEdits ?? 0)} edits)`);
243
+ return result;
244
+ },
239
245
  onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path),
240
246
  });
241
- const wrappedHandler = wrapToolHandler(handler, {
242
- guard: options.isInitialized,
247
+ registerStandardTool(server, EDIT_FILE_TOOL, handler, options, {
243
248
  progressMessage: buildEditProgressMessage,
244
249
  completionMessage: buildEditCompletionMessage,
245
250
  });
246
- const validatedHandler = withValidatedArgs(EditFileInputSchema, wrappedHandler);
247
- if (registerToolTaskIfAvailable(server, 'edit', EDIT_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
248
- return;
249
- server.registerTool('edit', withDefaultIcons({ ...EDIT_FILE_TOOL }, options.iconInfo), validatedHandler);
250
251
  }
@@ -0,0 +1,15 @@
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[];
@@ -0,0 +1,24 @@
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 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const LIST_DIRECTORY_TOOL: ToolContract;
4
4
  export declare function registerListDirectoryTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -7,8 +7,9 @@ import { listDirectory } from '../lib/file-operations/metadata.js';
7
7
  import { createBase64JsonCodec } from '../lib/zod-codecs.js';
8
8
  import { formatOperationSummary, joinLines } from '../config.js';
9
9
  import { ListDirectoryInputSchema, ListDirectoryOutputSchema, } from '../schemas.js';
10
- import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
11
- import { registerToolTaskIfAvailable } from './task-support.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';
12
13
  export const LIST_DIRECTORY_TOOL = {
13
14
  name: 'ls',
14
15
  title: 'List Directory',
@@ -18,6 +19,7 @@ export const LIST_DIRECTORY_TOOL = {
18
19
  inputSchema: ListDirectoryInputSchema,
19
20
  outputSchema: ListDirectoryOutputSchema,
20
21
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
22
+ icons: DIRECTORY_ICONS,
21
23
  taskSupport: 'optional',
22
24
  nuances: ['`pattern` enables filtered recursive traversal up to `maxDepth`.'],
23
25
  };
@@ -194,16 +196,15 @@ async function handleListDirectory(args, signal) {
194
196
  return buildToolResponse(buildListTextResult(displayResult, nextCursor), buildStructuredListResult(displayResult, nextCursor));
195
197
  }
196
198
  export function registerListDirectoryTool(server, options = {}) {
197
- const handler = (args, extra) => executeToolWithDiagnostics({
199
+ const handler = (args, ctx) => executeToolWithDiagnostics({
198
200
  toolName: 'ls',
199
- extra,
201
+ ctx,
200
202
  outputSchema: ListDirectoryOutputSchema,
201
203
  context: { path: args.path ?? '.' },
202
204
  run: (signal) => handleListDirectory(args, signal),
203
205
  onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_DIRECTORY, args.path ?? '.'),
204
206
  });
205
- const wrappedHandler = wrapToolHandler(handler, {
206
- guard: options.isInitialized,
207
+ registerStandardTool(server, LIST_DIRECTORY_TOOL, handler, options, {
207
208
  progressMessage: (args) => `≣ ls: ${args.path ? basename(args.path) : '.'}`,
208
209
  completionMessage: (args, result) => {
209
210
  const base = args.path ? basename(args.path) : '.';
@@ -214,8 +215,4 @@ export function registerListDirectoryTool(server, options = {}) {
214
215
  return `≣ ls: ${base} • ${count} ${count === 1 ? 'entry' : 'entries'}`;
215
216
  },
216
217
  });
217
- const validatedHandler = withValidatedArgs(ListDirectoryInputSchema, wrappedHandler);
218
- if (registerToolTaskIfAvailable(server, 'ls', LIST_DIRECTORY_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
219
- return;
220
- server.registerTool('ls', withDefaultIcons({ ...LIST_DIRECTORY_TOOL }, options.iconInfo), validatedHandler);
221
218
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const MOVE_FILE_TOOL: ToolContract;
4
4
  export declare function registerMoveFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -5,8 +5,9 @@ import { ErrorCode, formatUnknownErrorMessage, isNodeError, McpError, } from '..
5
5
  import { Logger } from '../lib/logger.js';
6
6
  import { assertAllowedFileAccess, validateExistingPath, validatePathForWrite, } from '../lib/paths.js';
7
7
  import { MoveFileInputSchema, MoveFileOutputSchema } from '../schemas.js';
8
- import { buildStructuredError, buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
9
- import { registerToolTaskIfAvailable } from './task-support.js';
8
+ import { FILE_MOVE_ICONS } from './icons.js';
9
+ import { buildStructuredError, buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, } from './shared.js';
10
+ import { registerStandardTool } from './task-support.js';
10
11
  export const MOVE_FILE_TOOL = {
11
12
  name: 'mv',
12
13
  title: 'Move File',
@@ -14,6 +15,7 @@ export const MOVE_FILE_TOOL = {
14
15
  inputSchema: MoveFileInputSchema,
15
16
  outputSchema: MoveFileOutputSchema,
16
17
  annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
18
+ icons: FILE_MOVE_ICONS,
17
19
  gotchas: [
18
20
  'On POSIX, an existing destination is silently overwritten; on Windows, rename fails with EEXIST if destination exists.',
19
21
  ],
@@ -138,17 +140,20 @@ async function handleMoveFile(args, signal) {
138
140
  });
139
141
  }
140
142
  export function registerMoveFileTool(server, options = {}) {
141
- const handler = (args, extra) => executeToolWithDiagnostics({
143
+ const handler = (args, ctx) => executeToolWithDiagnostics({
142
144
  toolName: 'mv',
143
- extra,
145
+ ctx,
144
146
  outputSchema: MoveFileOutputSchema,
145
147
  timedSignal: {},
146
148
  context: { path: args.source ?? args.sources?.[0] },
147
- run: (signal) => handleMoveFile(args, signal),
149
+ run: async (signal) => {
150
+ const result = await handleMoveFile(args, signal);
151
+ void ctx.log?.('info', `mv: ${args.source ?? args.sources?.join(', ') ?? ''} \u2192 ${args.destination}`);
152
+ return result;
153
+ },
148
154
  onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.source ?? args.sources?.[0]),
149
155
  });
150
- const wrappedHandler = wrapToolHandler(handler, {
151
- guard: options.isInitialized,
156
+ registerStandardTool(server, MOVE_FILE_TOOL, handler, options, {
152
157
  progressMessage: (args) => {
153
158
  const dest = basename(args.destination);
154
159
  if (args.source && !args.sources?.length) {
@@ -171,8 +176,4 @@ export function registerMoveFileTool(server, options = {}) {
171
176
  return `🛠 mv: ${count} items → ${dest}`;
172
177
  },
173
178
  });
174
- const validatedHandler = withValidatedArgs(MoveFileInputSchema, wrappedHandler);
175
- if (registerToolTaskIfAvailable(server, 'mv', MOVE_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
176
- return;
177
- server.registerTool('mv', withDefaultIcons({ ...MOVE_FILE_TOOL }, options.iconInfo), validatedHandler);
178
179
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const READ_MANY_TOOL: ToolContract;
4
4
  export declare function registerReadMultipleFilesTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -3,8 +3,9 @@ import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
3
3
  import { ErrorCode } from '../lib/errors.js';
4
4
  import { readMultipleFiles } from '../lib/file-operations/metadata.js';
5
5
  import { ReadMultipleFilesInputSchema, ReadMultipleFilesOutputSchema, } from '../schemas.js';
6
- import { buildBatchCompletionSuffix, buildBatchPathContext, buildResourceLink, buildStructuredError, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
7
- import { registerToolTaskIfAvailable } from './task-support.js';
6
+ import { FILE_READ_ICONS } from './icons.js';
7
+ import { buildBatchCompletionSuffix, buildBatchPathContext, buildResourceLink, buildStructuredError, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, } from './shared.js';
8
+ import { registerStandardTool } from './task-support.js';
8
9
  const READ_MANY_TOOL_NAME = 'read_many';
9
10
  const READ_MANY_TOOL_LABEL = '🕮 read_many';
10
11
  const FULL_FILE_CONTENTS_DESCRIPTION = 'Full file contents';
@@ -16,6 +17,7 @@ export const READ_MANY_TOOL = {
16
17
  inputSchema: ReadMultipleFilesInputSchema,
17
18
  outputSchema: ReadMultipleFilesOutputSchema,
18
19
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
20
+ icons: FILE_READ_ICONS,
19
21
  taskSupport: 'optional',
20
22
  };
21
23
  function buildReadManyResourceName(filePath) {
@@ -161,17 +163,17 @@ async function handleReadMultipleFiles(args, signal, resourceStore, onReadComple
161
163
  return buildToolResponse(payload.text, structured, payload.resourceLinks);
162
164
  }
163
165
  export function registerReadMultipleFilesTool(server, options = {}) {
164
- const handler = (args, extra) => {
166
+ const handler = (args, ctx) => {
165
167
  const primaryPath = args.paths[0] ?? '';
166
168
  return executeToolWithDiagnostics({
167
169
  toolName: READ_MANY_TOOL_NAME,
168
- extra,
170
+ ctx,
169
171
  outputSchema: ReadMultipleFilesOutputSchema,
170
172
  timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
171
173
  context: { path: primaryPath },
172
174
  run: async (signal) => {
173
175
  const context = buildBatchPathContext(args.paths, 'files');
174
- const { progress, onItemComplete } = createBatchProgressCallbacks(extra, {
176
+ const { progress, onItemComplete } = createBatchProgressCallbacks(ctx, {
175
177
  toolLabel: READ_MANY_TOOL_LABEL,
176
178
  context,
177
179
  totalItems: args.paths.length,
@@ -194,11 +196,5 @@ export function registerReadMultipleFilesTool(server, options = {}) {
194
196
  onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FILE, primaryPath),
195
197
  });
196
198
  };
197
- const wrappedHandler = wrapToolHandler(handler, {
198
- guard: options.isInitialized,
199
- });
200
- const validatedHandler = withValidatedArgs(ReadMultipleFilesInputSchema, wrappedHandler);
201
- if (registerToolTaskIfAvailable(server, READ_MANY_TOOL_NAME, READ_MANY_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
202
- return;
203
- server.registerTool(READ_MANY_TOOL_NAME, withDefaultIcons({ ...READ_MANY_TOOL }, options.iconInfo), validatedHandler);
199
+ registerStandardTool(server, READ_MANY_TOOL, handler, options);
204
200
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const READ_FILE_TOOL: ToolContract;
4
4
  export declare function registerReadFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -4,8 +4,9 @@ import { DEFAULT_SEARCH_TIMEOUT_MS, MAX_TEXT_FILE_SIZE, } from '../lib/constants
4
4
  import { ErrorCode } from '../lib/errors.js';
5
5
  import { readFile } from '../lib/fs-helpers.js';
6
6
  import { ReadFileInputSchema, ReadFileOutputSchema } from '../schemas.js';
7
- import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
8
- import { registerToolTaskIfAvailable } from './task-support.js';
7
+ import { FILE_READ_ICONS } from './icons.js';
8
+ import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, } from './shared.js';
9
+ import { registerStandardTool } from './task-support.js';
9
10
  export const READ_FILE_TOOL = {
10
11
  name: 'read',
11
12
  title: 'Read File',
@@ -15,6 +16,7 @@ export const READ_FILE_TOOL = {
15
16
  inputSchema: ReadFileInputSchema,
16
17
  outputSchema: ReadFileOutputSchema,
17
18
  annotations: READ_ONLY_TOOL_ANNOTATIONS,
19
+ icons: FILE_READ_ICONS,
18
20
  nuances: [
19
21
  'Large content is externalized to `filesystem-mcp://result/{id}` and preview is returned inline.',
20
22
  ],
@@ -153,22 +155,17 @@ async function handleReadFile(args, signal, resourceStore) {
153
155
  return buildToolResponse(result.content, structured);
154
156
  }
155
157
  export function registerReadFileTool(server, options = {}) {
156
- const handler = (args, extra) => executeToolWithDiagnostics({
158
+ const handler = (args, ctx) => executeToolWithDiagnostics({
157
159
  toolName: READ_TOOL_NAME,
158
- extra,
160
+ ctx,
159
161
  outputSchema: ReadFileOutputSchema,
160
162
  timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
161
163
  context: { path: args.path },
162
164
  run: (signal) => handleReadFile(args, signal, options.resourceStore),
163
165
  onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FILE, args.path),
164
166
  });
165
- const wrappedHandler = wrapToolHandler(handler, {
166
- guard: options.isInitialized,
167
+ registerStandardTool(server, READ_FILE_TOOL, handler, options, {
167
168
  progressMessage: buildReadProgressMessage,
168
169
  completionMessage: buildReadCompletionMessage,
169
170
  });
170
- const validatedHandler = withValidatedArgs(ReadFileInputSchema, wrappedHandler);
171
- if (registerToolTaskIfAvailable(server, READ_TOOL_NAME, READ_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
172
- return;
173
- server.registerTool(READ_TOOL_NAME, withDefaultIcons({ ...READ_FILE_TOOL }, options.iconInfo), validatedHandler);
174
171
  }
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const SEARCH_AND_REPLACE_TOOL: ToolContract;
4
4
  export declare function registerSearchAndReplaceTool(server: McpServer, options?: ToolRegistrationOptions): void;
@@ -10,8 +10,9 @@ import { atomicWriteFile } from '../lib/fs-helpers.js';
10
10
  import { Logger } from '../lib/logger.js';
11
11
  import { validateExistingPath, validatePathForWrite } from '../lib/paths.js';
12
12
  import { SearchAndReplaceInputSchema, SearchAndReplaceOutputSchema, } from '../schemas.js';
13
- import { buildStructuredError, buildToolErrorResponse, buildToolResponse, createToolProgressSession, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, resolveFinalProgressCurrent, resolvePathOrRoot, truncateProgressPattern, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
14
- import { registerToolTaskIfAvailable } from './task-support.js';
13
+ import { FILE_EDIT_ICONS } from './icons.js';
14
+ import { buildStructuredError, buildToolErrorResponse, buildToolResponse, createToolProgressSession, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, resolveFinalProgressCurrent, resolvePathOrRoot, truncateProgressPattern, } from './shared.js';
15
+ import { registerStandardTool } from './task-support.js';
15
16
  export const SEARCH_AND_REPLACE_TOOL = {
16
17
  name: 'search_and_replace',
17
18
  title: 'Search and Replace',
@@ -22,6 +23,7 @@ export const SEARCH_AND_REPLACE_TOOL = {
22
23
  inputSchema: SearchAndReplaceInputSchema,
23
24
  outputSchema: SearchAndReplaceOutputSchema,
24
25
  annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
26
+ icons: FILE_EDIT_ICONS,
25
27
  taskSupport: 'optional',
26
28
  };
27
29
  const MAX_FAILURES = 20;
@@ -328,9 +330,9 @@ async function handleSearchAndReplace(args, signal, onProgress = () => { }) {
328
330
  return buildToolResponse(buildSearchAndReplaceText(summary, args.dryRun), buildSearchAndReplaceStructuredResult(summary, args));
329
331
  }
330
332
  export function registerSearchAndReplaceTool(server, options = {}) {
331
- const handler = (args, extra) => executeToolWithDiagnostics({
333
+ const handler = (args, ctx) => executeToolWithDiagnostics({
332
334
  toolName: 'search_and_replace',
333
- extra,
335
+ ctx,
334
336
  outputSchema: SearchAndReplaceOutputSchema,
335
337
  timedSignal: {},
336
338
  ...(args.path ? { context: { path: args.path } } : {}),
@@ -338,7 +340,7 @@ export function registerSearchAndReplaceTool(server, options = {}) {
338
340
  const dryLabel = args.dryRun ? ' [dry run]' : '';
339
341
  const truncatedPattern = truncateProgressPattern(args.searchPattern);
340
342
  const context = `"${truncatedPattern}" in ${args.filePattern}${dryLabel}`;
341
- const progress = createToolProgressSession(extra, `🛠 replace: ${context}`);
343
+ const progress = createToolProgressSession(ctx, `🛠 replace: ${context}`);
342
344
  const progressWithMessage = ({ current, total, }) => {
343
345
  progress.update({
344
346
  current,
@@ -356,6 +358,9 @@ export function registerSearchAndReplaceTool(server, options = {}) {
356
358
  if (sc.failedFiles)
357
359
  endSuffix += `, ${sc.failedFiles} failed`;
358
360
  progress.complete(`🛠 replace: ${context} • ${endSuffix}`, finalCurrent);
361
+ if (!args.dryRun) {
362
+ void ctx.log?.('info', `search_and_replace: ${String(sc.matches ?? 0)} matches in ${String(sc.filesChanged ?? 0)} files`);
363
+ }
359
364
  return result;
360
365
  }
361
366
  catch (error) {
@@ -365,14 +370,7 @@ export function registerSearchAndReplaceTool(server, options = {}) {
365
370
  },
366
371
  onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path),
367
372
  });
368
- const { isInitialized } = options;
369
- const wrappedHandler = wrapToolHandler(handler, {
370
- guard: isInitialized,
371
- });
372
- const validatedHandler = withValidatedArgs(SearchAndReplaceInputSchema, wrappedHandler);
373
- if (registerToolTaskIfAvailable(server, 'search_and_replace', SEARCH_AND_REPLACE_TOOL, validatedHandler, options.iconInfo, isInitialized))
374
- return;
375
- server.registerTool('search_and_replace', withDefaultIcons({ ...SEARCH_AND_REPLACE_TOOL }, options.iconInfo), validatedHandler);
373
+ registerStandardTool(server, SEARCH_AND_REPLACE_TOOL, handler, options);
376
374
  }
377
375
  function buildSearchAndReplaceText(summary, dryRun) {
378
376
  const failureSuffix = summary.failedFiles > 0 ? ` (${summary.failedFiles} failed)` : '';
@@ -1,4 +1,4 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
1
+ import type { McpServer } from '@modelcontextprotocol/server';
2
2
  import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
3
3
  export declare const LIST_ALLOWED_DIRECTORIES_TOOL: ToolContract;
4
4
  export declare function registerListAllowedDirectoriesTool(server: McpServer, options?: ToolRegistrationOptions): void;