@j0hanz/filesystem-mcp 1.11.0 → 1.12.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 (42) hide show
  1. package/dist/completions.js +25 -0
  2. package/dist/lib/constants.d.ts +2 -0
  3. package/dist/lib/constants.js +2 -0
  4. package/dist/lib/file-operations/metadata.js +71 -142
  5. package/dist/lib/file-operations/search.js +119 -19
  6. package/dist/lib/file-operations/traversal.js +45 -102
  7. package/dist/lib/fs-helpers.js +175 -158
  8. package/dist/prompts.d.ts +1 -0
  9. package/dist/prompts.js +58 -0
  10. package/dist/resources/generated-instructions.js +11 -3
  11. package/dist/resources/tool-catalog.js +49 -3
  12. package/dist/resources/tool-info.d.ts +1 -0
  13. package/dist/resources/tool-info.js +139 -8
  14. package/dist/resources.js +2 -0
  15. package/dist/schemas.d.ts +2 -0
  16. package/dist/schemas.js +10 -4
  17. package/dist/server/bootstrap.js +41 -5
  18. package/dist/tools/apply-patch.js +30 -33
  19. package/dist/tools/calculate-hash.js +21 -24
  20. package/dist/tools/contract.d.ts +1 -1
  21. package/dist/tools/create-directory.js +3 -3
  22. package/dist/tools/delete-file.js +2 -2
  23. package/dist/tools/diff-files.js +32 -20
  24. package/dist/tools/edit-file.d.ts +4 -1
  25. package/dist/tools/edit-file.js +172 -104
  26. package/dist/tools/list-directory.js +105 -10
  27. package/dist/tools/move-file.js +3 -3
  28. package/dist/tools/read-multiple.d.ts +1 -1
  29. package/dist/tools/read-multiple.js +123 -99
  30. package/dist/tools/read.js +104 -76
  31. package/dist/tools/replace-in-files.d.ts +5 -2
  32. package/dist/tools/replace-in-files.js +142 -76
  33. package/dist/tools/roots.js +1 -1
  34. package/dist/tools/search-content.js +249 -218
  35. package/dist/tools/shared.js +39 -7
  36. package/dist/tools/stat-many.js +12 -19
  37. package/dist/tools/stat.js +1 -1
  38. package/dist/tools/task-support.d.ts +7 -0
  39. package/dist/tools/task-support.js +140 -101
  40. package/dist/tools/write-file.js +4 -4
  41. package/dist/tools.js +2 -2
  42. package/package.json +1 -1
@@ -1,11 +1,22 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
1
2
  import { channel } from 'node:diagnostics_channel';
3
+ import { performance } from 'node:perf_hooks';
4
+ import { format } from 'node:util';
2
5
  import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
3
- import { DEFAULT_TASK_TTL_MS } from '../lib/constants.js';
6
+ import { DEFAULT_TASK_TTL_MS, MAX_CONCURRENT_TASKS, MAX_TASK_TTL_MS, } from '../lib/constants.js';
4
7
  import { ErrorCode, McpError } from '../lib/errors.js';
5
8
  import { isRecord } from '../lib/utils.js';
6
9
  import { buildToolErrorResponse, maybeStripStructuredContentFromResult, withDefaultIcons, } from './shared.js';
10
+ export const taskContext = new AsyncLocalStorage();
11
+ const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
12
+ function publishTaskDiagnostics(event) {
13
+ if (TASK_DIAGNOSTICS_CHANNEL.hasSubscribers) {
14
+ TASK_DIAGNOSTICS_CHANNEL.publish(event);
15
+ }
16
+ }
17
+ // --- Type Guards & Helpers ---
7
18
  function isExperimentalTaskRegistration(value) {
8
- if (!value || typeof value !== 'object')
19
+ if (!isRecord(value))
9
20
  return false;
10
21
  const { registerToolTask } = value;
11
22
  return (registerToolTask === undefined || typeof registerToolTask === 'function');
@@ -13,7 +24,7 @@ function isExperimentalTaskRegistration(value) {
13
24
  function getExperimentalTaskRegistration(server) {
14
25
  const serverWithExperimental = server;
15
26
  const { experimental } = serverWithExperimental;
16
- if (!experimental || typeof experimental !== 'object')
27
+ if (!isRecord(experimental))
17
28
  return undefined;
18
29
  const { tasks } = experimental;
19
30
  if (!isExperimentalTaskRegistration(tasks))
@@ -24,49 +35,34 @@ function hasTaskToolCapability(server) {
24
35
  const serverRecord = server;
25
36
  const { server: serverRuntime } = serverRecord;
26
37
  if (!isRecord(serverRuntime))
27
- return true;
38
+ return true; // Assume capability if runtime structure is opaque
28
39
  const { getCapabilities } = serverRuntime;
29
- if (typeof getCapabilities !== 'function') {
30
- // Fallback for tests or custom wrappers that provide only registerTool/experimental.
40
+ if (typeof getCapabilities !== 'function')
31
41
  return true;
32
- }
33
42
  const capabilities = getCapabilities.call(serverRuntime);
34
43
  if (!isRecord(capabilities))
35
44
  return false;
36
45
  const { tasks } = capabilities;
37
- if (!isRecord(tasks))
38
- return false;
39
- const { requests } = tasks;
40
- if (!isRecord(requests))
41
- return false;
42
- const { tools } = requests;
43
- if (!isRecord(tools))
44
- return false;
45
- const { call } = tools;
46
- return isRecord(call);
46
+ return (isRecord(tasks) &&
47
+ isRecord(tasks.requests) &&
48
+ isRecord(tasks.requests.tools) &&
49
+ isRecord(tasks.requests.tools.call));
47
50
  }
48
51
  const TASK_STATUS_NOTIFICATION_METHOD = 'notifications/tasks/status';
49
- const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
50
- function publishTaskDiagnostics(event) {
51
- if (!TASK_DIAGNOSTICS_CHANNEL.hasSubscribers)
52
- return;
53
- TASK_DIAGNOSTICS_CHANNEL.publish(event);
54
- }
55
52
  function isRequestTaskStore(value) {
56
- if (!isRecord(value))
57
- return false;
58
- return (typeof value['createTask'] === 'function' &&
59
- typeof value['getTask'] === 'function' &&
60
- typeof value['storeTaskResult'] === 'function' &&
61
- typeof value['getTaskResult'] === 'function');
53
+ return (isRecord(value) &&
54
+ typeof value.createTask === 'function' &&
55
+ typeof value.getTask === 'function' &&
56
+ typeof value.storeTaskResult === 'function' &&
57
+ typeof value.getTaskResult === 'function');
62
58
  }
63
59
  function isCreateTaskExtra(value) {
64
- return isRecord(value) && isRequestTaskStore(value['taskStore']);
60
+ return isRecord(value) && isRequestTaskStore(value.taskStore);
65
61
  }
66
62
  function isTaskExtra(value) {
67
63
  return (isCreateTaskExtra(value) &&
68
- typeof value['taskId'] === 'string' &&
69
- value['taskId'].length > 0);
64
+ typeof value.taskId === 'string' &&
65
+ value.taskId.length > 0);
70
66
  }
71
67
  function asCreateTaskExtra(value) {
72
68
  if (!isCreateTaskExtra(value)) {
@@ -91,39 +87,34 @@ function isTaskStatus(value) {
91
87
  return (typeof value === 'string' &&
92
88
  TASK_STATUSES.has(value));
93
89
  }
94
- function parseTaskStatus(value) {
95
- return isTaskStatus(value) ? value : undefined;
96
- }
97
90
  function normalizeGetTaskResult(value) {
98
- if (!isRecord(value) || typeof value['taskId'] !== 'string') {
91
+ if (!isRecord(value) || typeof value.taskId !== 'string') {
99
92
  throw new McpError(ErrorCode.E_INVALID_INPUT, 'Invalid task object.');
100
93
  }
101
- const status = parseTaskStatus(value['status']);
94
+ const status = isTaskStatus(value.status) ? value.status : undefined;
102
95
  if (!status) {
103
96
  throw new McpError(ErrorCode.E_INVALID_INPUT, 'Invalid task status.');
104
97
  }
105
- const createdAt = typeof value['createdAt'] === 'string'
106
- ? value['createdAt']
98
+ const createdAt = typeof value.createdAt === 'string'
99
+ ? value.createdAt
107
100
  : new Date().toISOString();
108
- const lastUpdatedAt = typeof value['lastUpdatedAt'] === 'string'
109
- ? value['lastUpdatedAt']
110
- : createdAt;
111
- const ttl = typeof value['ttl'] === 'number' ? value['ttl'] : null;
101
+ const lastUpdatedAt = typeof value.lastUpdatedAt === 'string' ? value.lastUpdatedAt : createdAt;
102
+ const ttl = typeof value.ttl === 'number' ? value.ttl : null;
112
103
  const normalized = {
113
- taskId: value['taskId'],
104
+ taskId: value.taskId,
114
105
  status,
115
106
  ttl,
116
107
  createdAt,
117
108
  lastUpdatedAt,
118
109
  };
119
- if (typeof value['pollInterval'] === 'number') {
120
- normalized.pollInterval = value['pollInterval'];
110
+ if (typeof value.pollInterval === 'number') {
111
+ normalized.pollInterval = value.pollInterval;
121
112
  }
122
- if (typeof value['statusMessage'] === 'string') {
123
- normalized.statusMessage = value['statusMessage'];
113
+ if (typeof value.statusMessage === 'string') {
114
+ normalized.statusMessage = value.statusMessage;
124
115
  }
125
- if (isRecord(value['_meta'])) {
126
- normalized._meta = value['_meta'];
116
+ if (isRecord(value._meta)) {
117
+ normalized._meta = value._meta;
127
118
  }
128
119
  return normalized;
129
120
  }
@@ -134,17 +125,15 @@ function normalizeCallToolResult(value) {
134
125
  throw new McpError(ErrorCode.E_INVALID_INPUT, 'Stored task result is not a valid tool result.');
135
126
  }
136
127
  function getToolResultErrorCode(result) {
137
- if (!isRecord(result) || result['isError'] !== true)
128
+ if (!isRecord(result) || result.isError !== true)
138
129
  return undefined;
139
- // First check for a dedicated errorCode property to avoid regex parsing of the content for structured error results produced by newer code.
140
- if (typeof result['errorCode'] === 'string')
141
- return result['errorCode'];
142
- // Fallback to regex parsing of the human-readable error message for older error results that lack a structured errorCode property.
130
+ if (typeof result.errorCode === 'string')
131
+ return result.errorCode;
143
132
  const { content } = result;
144
133
  if (!Array.isArray(content) || content.length === 0)
145
134
  return undefined;
146
135
  const first = content[0];
147
- if (!isRecord(first) || first['type'] !== 'text')
136
+ if (!isRecord(first) || first.type !== 'text')
148
137
  return undefined;
149
138
  const { text } = first;
150
139
  if (typeof text !== 'string')
@@ -155,6 +144,25 @@ function getToolResultErrorCode(result) {
155
144
  function isCancelledToolResult(result) {
156
145
  return getToolResultErrorCode(result) === ErrorCode.E_CANCELLED;
157
146
  }
147
+ function resolveTaskResultStatuses(result) {
148
+ if (isCancelledToolResult(result)) {
149
+ return { storedStatus: 'failed', reportedStatus: 'cancelled' };
150
+ }
151
+ if (isRecord(result) && result.isError === true) {
152
+ return { storedStatus: 'failed', reportedStatus: 'failed' };
153
+ }
154
+ return { storedStatus: 'completed', reportedStatus: 'completed' };
155
+ }
156
+ function attachRelatedTaskMeta(result, taskId) {
157
+ const existingMeta = isRecord(result._meta) ? result._meta : {};
158
+ return {
159
+ ...result,
160
+ _meta: {
161
+ ...existingMeta,
162
+ 'io.modelcontextprotocol/related-task': { taskId },
163
+ },
164
+ };
165
+ }
158
166
  async function projectCancelledTaskStatus(taskStore, task) {
159
167
  if (task.status !== 'failed')
160
168
  return task;
@@ -199,14 +207,14 @@ async function notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName) {
199
207
  phase: 'task_status_notified',
200
208
  taskId,
201
209
  status: normalized.status,
202
- ...(toolName !== undefined ? { toolName } : {}),
210
+ ...(toolName ? { toolName } : {}),
203
211
  });
204
212
  }
205
213
  catch {
206
214
  publishTaskDiagnostics({
207
215
  phase: 'task_status_notify_failed',
208
216
  taskId,
209
- ...(toolName !== undefined ? { toolName } : {}),
217
+ ...(toolName ? { toolName } : {}),
210
218
  });
211
219
  // Never fail task execution because status notifications are optional.
212
220
  }
@@ -226,7 +234,7 @@ function getTaskId(extra) {
226
234
  function isErrorResult(result) {
227
235
  return 'isError' in result && result.isError;
228
236
  }
229
- // Strips structuredContent from a tool result if present, without modifying the original object. This is used when storing error results as 'completed' to prevent client-side output schema validation errors, while still allowing the human-readable error message in content[0].text to be returned to clients.
237
+ // Strips structuredContent from a tool result if present, without modifying the original object.
230
238
  function withoutStructuredContent(result) {
231
239
  if (!Object.hasOwn(result, 'structuredContent'))
232
240
  return result;
@@ -251,61 +259,89 @@ async function isTaskAlreadyTerminal(taskStore, taskId) {
251
259
  return false;
252
260
  }
253
261
  }
254
- async function tryStoreTaskResult(taskStore, taskId, status, result) {
262
+ async function countActiveTasks(taskStore) {
263
+ if (typeof taskStore.listTasks !== 'function')
264
+ return 0;
265
+ const listed = await taskStore.listTasks();
266
+ const tasks = Array.isArray(listed.tasks) ? listed.tasks : [];
267
+ let active = 0;
268
+ for (const task of tasks) {
269
+ if (!isRecord(task) || typeof task.status !== 'string')
270
+ continue;
271
+ if (task.status !== 'completed' &&
272
+ task.status !== 'failed' &&
273
+ task.status !== 'cancelled') {
274
+ active += 1;
275
+ }
276
+ }
277
+ return active;
278
+ }
279
+ function resolveRequestedTaskTtl(requestedTtl) {
280
+ if (requestedTtl == null)
281
+ return DEFAULT_TASK_TTL_MS;
282
+ if (!Number.isFinite(requestedTtl)) {
283
+ throw new McpError(ErrorCode.E_INVALID_INPUT, 'Task ttl must be a finite number of milliseconds.');
284
+ }
285
+ const normalized = Math.trunc(requestedTtl);
286
+ if (normalized <= 0) {
287
+ throw new McpError(ErrorCode.E_INVALID_INPUT, 'Task ttl must be greater than zero.');
288
+ }
289
+ return Math.min(normalized, MAX_TASK_TTL_MS);
290
+ }
291
+ async function safelyStoreTaskResult(taskStore, taskId, storedStatus, result) {
255
292
  try {
256
- await taskStore.storeTaskResult(taskId, status, result);
293
+ await taskStore.storeTaskResult(taskId, storedStatus, result);
257
294
  }
258
295
  catch (error) {
296
+ // If task was already cancelled/failed by another process, ignore the write error
259
297
  if (await isTaskAlreadyTerminal(taskStore, taskId))
260
298
  return;
261
299
  throw error;
262
300
  }
263
301
  }
264
302
  async function runTaskInBackground(run, args, extra, taskStore, taskId, toolName) {
303
+ const start = performance.now();
304
+ let taskStatuses;
305
+ let result;
265
306
  try {
266
- const rawResult = await run(args, extra);
267
- const status = isCancelledToolResult(rawResult) ? 'failed' : 'completed';
268
- const result = isErrorResult(rawResult) && status === 'completed'
307
+ const rawResult = await taskContext.run({ taskId, toolName, startTime: start }, () => run(args, extra));
308
+ taskStatuses = resolveTaskResultStatuses(rawResult);
309
+ result = isErrorResult(rawResult)
269
310
  ? withoutStructuredContent(rawResult)
270
311
  : maybeStripStructuredContentFromResult(rawResult);
271
- await tryStoreTaskResult(taskStore, taskId, status, result);
312
+ }
313
+ catch (error) {
314
+ taskStatuses = { storedStatus: 'failed', reportedStatus: 'failed' };
315
+ result = maybeStripStructuredContentFromResult(buildToolErrorResponse(error, ErrorCode.E_UNKNOWN));
316
+ }
317
+ const durationMs = performance.now() - start;
318
+ try {
319
+ await safelyStoreTaskResult(taskStore, taskId, taskStatuses.storedStatus, result);
272
320
  publishTaskDiagnostics({
273
321
  phase: 'task_result_stored',
274
322
  taskId,
275
- status,
276
- ...(toolName !== undefined ? { toolName } : {}),
323
+ status: taskStatuses.reportedStatus,
324
+ toolName,
325
+ durationMs,
277
326
  });
278
327
  await notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName);
279
328
  }
280
- catch (error) {
281
- const fallback = maybeStripStructuredContentFromResult(buildToolErrorResponse(error, ErrorCode.E_UNKNOWN));
282
- try {
283
- await tryStoreTaskResult(taskStore, taskId, 'failed', fallback);
284
- publishTaskDiagnostics({
285
- phase: 'task_result_stored',
286
- taskId,
287
- status: 'failed',
288
- ...(toolName !== undefined ? { toolName } : {}),
329
+ catch (innerError) {
330
+ console.error(format('Failed to store task result for task %s:', taskId), innerError);
331
+ const syntheticTask = {
332
+ taskId,
333
+ status: taskStatuses.reportedStatus === 'cancelled' ? 'cancelled' : 'failed',
334
+ ttl: null,
335
+ createdAt: new Date().toISOString(),
336
+ lastUpdatedAt: new Date().toISOString(),
337
+ statusMessage: 'Internal system error while storing result',
338
+ };
339
+ const { sendNotification } = extra;
340
+ if (typeof sendNotification === 'function') {
341
+ void sendNotification({
342
+ method: TASK_STATUS_NOTIFICATION_METHOD,
343
+ params: buildTaskStatusNotificationParams(syntheticTask),
289
344
  });
290
- await notifyTaskStatusIfPossible(extra, taskStore, taskId, toolName);
291
- }
292
- catch (innerError) {
293
- console.error(`Failed to store task failure result for task ${taskId}:`, innerError);
294
- // If storing the failure result also fails, there's not much we can do. The task will remain in 'working' status until it expires, which is not ideal but at least prevents clients from receiving incorrect results or hanging indefinitely waiting for a result that will never arrive. We log the error to aid debugging, and we attempt to notify the client of the failure if possible, but we don't want to throw further errors that could crash the server or cause cascading failures.
295
- const syntheticTask = {
296
- taskId,
297
- status: 'failed',
298
- ttl: null,
299
- createdAt: new Date().toISOString(),
300
- lastUpdatedAt: new Date().toISOString(),
301
- };
302
- const { sendNotification } = extra;
303
- if (typeof sendNotification === 'function') {
304
- void sendNotification({
305
- method: TASK_STATUS_NOTIFICATION_METHOD,
306
- params: buildTaskStatusNotificationParams(syntheticTask),
307
- });
308
- }
309
345
  }
310
346
  }
311
347
  }
@@ -324,7 +360,9 @@ function tryRegisterToolTask(server, toolName, toolDef, taskHandler, iconInfo) {
324
360
  const existingExecution = def.execution ?? {};
325
361
  const taskSupport = def.taskSupport ??
326
362
  existingExecution.taskSupport ??
327
- 'optional';
363
+ 'forbidden';
364
+ if (taskSupport === 'forbidden')
365
+ return false;
328
366
  tasks.registerToolTask(toolName, withDefaultIcons({ ...toolDef, execution: { ...existingExecution, taskSupport } }, iconInfo), taskHandler);
329
367
  return true;
330
368
  }
@@ -343,16 +381,17 @@ export function createToolTaskHandler(run, options) {
343
381
  throw new McpError(ErrorCode.E_INVALID_INPUT, 'Client not initialized; wait for notifications/initialized');
344
382
  }
345
383
  const taskStore = getTaskStore(extra);
384
+ if ((await countActiveTasks(taskStore)) >= MAX_CONCURRENT_TASKS) {
385
+ throw new McpError(ErrorCode.E_INVALID_INPUT, `Too many active tasks. Limit: ${String(MAX_CONCURRENT_TASKS)}.`);
386
+ }
346
387
  const task = await taskStore.createTask({
347
- ttl: extra.taskRequestedTtl ?? DEFAULT_TASK_TTL_MS,
388
+ ttl: resolveRequestedTaskTtl(extra.taskRequestedTtl),
348
389
  });
349
390
  publishTaskDiagnostics({
350
391
  phase: 'task_created',
351
392
  taskId: task.taskId,
352
393
  status: task.status,
353
- ...(options?.toolName !== undefined
354
- ? { toolName: options.toolName }
355
- : {}),
394
+ ...(options?.toolName ? { toolName: options.toolName } : {}),
356
395
  });
357
396
  const taskExtra = {
358
397
  ...extra,
@@ -375,7 +414,7 @@ export function createToolTaskHandler(run, options) {
375
414
  const taskStore = getTaskStore(extra);
376
415
  const taskId = getTaskId(extra);
377
416
  const result = await taskStore.getTaskResult(taskId);
378
- return normalizeCallToolResult(result);
417
+ return attachRelatedTaskMeta(normalizeCallToolResult(result), taskId);
379
418
  });
380
419
  return {
381
420
  createTask,
@@ -17,18 +17,18 @@ export const WRITE_FILE_TOOL = {
17
17
  gotchas: [
18
18
  '`write` replaces ALL existing content — use `edit` for partial updates.',
19
19
  ],
20
- taskSupport: 'optional',
20
+ taskSupport: 'forbidden',
21
21
  };
22
22
  async function handleWriteFile(args, signal) {
23
23
  const validPath = await validatePathForWrite(args.path, signal);
24
24
  // Ensure parent directory exists
25
25
  await withAbort(fs.mkdir(path.dirname(validPath), { recursive: true }), signal);
26
26
  await atomicWriteFile(validPath, args.content, { encoding: 'utf-8', signal });
27
- const stats = await withAbort(fs.stat(validPath), signal);
27
+ const bytesWritten = Buffer.byteLength(args.content, 'utf-8');
28
28
  return buildToolResponse(`Successfully wrote to file: ${args.path}`, {
29
29
  ok: true,
30
30
  path: validPath,
31
- bytesWritten: stats.size,
31
+ bytesWritten,
32
32
  });
33
33
  }
34
34
  export function registerWriteFileTool(server, options = {}) {
@@ -50,7 +50,7 @@ export function registerWriteFileTool(server, options = {}) {
50
50
  const sc = result.structuredContent;
51
51
  if (!sc.ok)
52
52
  return `🛠 write: ${name} • failed`;
53
- return `🛠 write: ${name} • ${formatBytes(sc.bytesWritten ?? 0)} written`;
53
+ return `🛠 write: ${name} • ${formatBytes(sc.bytesWritten ?? 0)}`;
54
54
  },
55
55
  });
56
56
  const validatedHandler = withValidatedArgs(WriteFileInputSchema, wrappedHandler);
package/dist/tools.js CHANGED
@@ -7,7 +7,7 @@ import { DIFF_FILES_TOOL, registerDiffFilesTool } from './tools/diff-files.js';
7
7
  import { EDIT_FILE_TOOL, registerEditFileTool } from './tools/edit-file.js';
8
8
  import { LIST_DIRECTORY_TOOL, registerListDirectoryTool, } from './tools/list-directory.js';
9
9
  import { MOVE_FILE_TOOL, registerMoveFileTool } from './tools/move-file.js';
10
- import { READ_MULTIPLE_FILES_TOOL, registerReadMultipleFilesTool, } from './tools/read-multiple.js';
10
+ import { READ_MANY_TOOL, registerReadMultipleFilesTool, } from './tools/read-multiple.js';
11
11
  import { READ_FILE_TOOL, registerReadFileTool } from './tools/read.js';
12
12
  import { registerSearchAndReplaceTool, SEARCH_AND_REPLACE_TOOL, } from './tools/replace-in-files.js';
13
13
  import { LIST_ALLOWED_DIRECTORIES_TOOL, registerListAllowedDirectoriesTool, } from './tools/roots.js';
@@ -28,7 +28,7 @@ const TOOL_ENTRIES = [
28
28
  { contract: TREE_TOOL, register: registerTreeTool },
29
29
  { contract: READ_FILE_TOOL, register: registerReadFileTool },
30
30
  {
31
- contract: READ_MULTIPLE_FILES_TOOL,
31
+ contract: READ_MANY_TOOL,
32
32
  register: registerReadMultipleFilesTool,
33
33
  },
34
34
  { contract: GET_FILE_INFO_TOOL, register: registerGetFileInfoTool },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/filesystem-mcp",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "mcpName": "io.github.j0hanz/filesystem-mcp",
5
5
  "description": "A local filesystem MCP server that lets LLMs and AI agents read, write, search, diff, patch, and manage files safely and efficiently. Built for reliable, structured, and controlled filesystem interaction.",
6
6
  "type": "module",