@j0hanz/filesystem-mcp 1.17.0 → 1.18.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.
@@ -3,7 +3,7 @@ import { basename } from 'node:path';
3
3
  import { z } from 'zod';
4
4
  import { createTimedAbortSignal } from '../lib/abort.js';
5
5
  import { parseTrueEnvFlag } from '../lib/constants.js';
6
- import { createDetailedError, ErrorCode, formatDetailedError, getSuggestion, McpError, } from '../lib/errors.js';
6
+ import { classifyError, createDetailedError, ErrorCode, formatDetailedError, getSuggestion, McpError, } from '../lib/errors.js';
7
7
  import { Logger } from '../lib/logger.js';
8
8
  import { withToolDiagnostics, } from '../lib/observability.js';
9
9
  import { getAllowedDirectories } from '../lib/paths.js';
@@ -223,20 +223,18 @@ export function withValidatedArgs(schema, handler) {
223
223
  }
224
224
  };
225
225
  }
226
- function toToolContext(ctx) {
226
+ export function toToolContext(ctx) {
227
227
  if (!ctx)
228
228
  return {};
229
229
  if ('mcpReq' in ctx) {
230
230
  return {
231
231
  signal: ctx.mcpReq.signal,
232
+ ...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),
232
233
  ...(ctx.mcpReq._meta
233
234
  ? { _meta: ctx.mcpReq._meta }
234
235
  : {}),
235
236
  sendNotification: async (notification) => ctx.mcpReq.notify(notification),
236
- log: async (level, data, logger) => ctx.mcpReq.notify({
237
- method: 'notifications/message',
238
- params: { level, data, ...(logger ? { logger } : {}) },
239
- }),
237
+ log: async (level, data, logger) => ctx.mcpReq.log(level, data, logger),
240
238
  };
241
239
  }
242
240
  return ctx;
@@ -509,7 +507,7 @@ async function withProgress(message, ctx, run, getCompletionMessage) {
509
507
  void reportProgress(ctx, {
510
508
  current: total,
511
509
  total,
512
- message: `${message} • failed`,
510
+ message: `${message} • ${classifyError(error)}`,
513
511
  });
514
512
  throw error;
515
513
  }
@@ -599,13 +597,3 @@ export function truncateProgressPattern(pattern, maxLength = 40) {
599
597
  }
600
598
  return `${pattern.slice(0, maxLength)}…`;
601
599
  }
602
- export function buildBatchCompletionSuffix(summary, successWord, singularWord) {
603
- const total = summary?.total ?? 0;
604
- const failed = summary?.failed ?? 0;
605
- const succeeded = summary?.succeeded ?? 0;
606
- if (failed) {
607
- return `${succeeded}/${total} ${successWord}, ${failed} failed`;
608
- }
609
- const word = total === 1 && singularWord ? singularWord : successWord;
610
- return `${total} ${word}`;
611
- }
@@ -1,10 +1,10 @@
1
1
  import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
2
- import { ErrorCode } from '../lib/errors.js';
2
+ import { classifyError, ErrorCode } from '../lib/errors.js';
3
3
  import { getMultipleFileInfo } from '../lib/file-operations/metadata.js';
4
4
  import { formatBytes, joinLines } from '../config.js';
5
5
  import { GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, } from '../schemas.js';
6
6
  import { FILE_READ_ICONS } from './icons.js';
7
- import { buildBatchCompletionSuffix, buildBatchPathContext, buildFileInfoPayload, buildStructuredError, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, } from './shared.js';
7
+ import { buildBatchPathContext, buildFileInfoPayload, buildStructuredError, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, } from './shared.js';
8
8
  import { registerStandardTool } from './task-support.js';
9
9
  export const GET_MULTIPLE_FILE_INFO_TOOL = {
10
10
  name: 'stat_many',
@@ -77,7 +77,7 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
77
77
  run: async (signal) => {
78
78
  const context = buildBatchPathContext(args.paths);
79
79
  const { progress, onItemComplete } = createBatchProgressCallbacks(ctx, {
80
- toolLabel: '🕮 stat_many',
80
+ toolLabel: GET_MULTIPLE_FILE_INFO_TOOL.title,
81
81
  context,
82
82
  totalItems: args.paths.length,
83
83
  itemVerb: 'done',
@@ -85,14 +85,15 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
85
85
  try {
86
86
  const result = await handleGetMultipleFileInfo(args, signal, onItemComplete);
87
87
  const sc = result.structuredContent;
88
- const suffix = buildBatchCompletionSuffix(sc.summary, 'OK');
89
88
  const total = sc.summary?.total ?? 0;
89
+ const failed = sc.summary?.failed ?? 0;
90
+ const suffix = failed ? `${failed} failed` : 'done';
90
91
  const finalCurrent = resolveFinalProgressCurrent(progress, total);
91
- progress.complete(`🕮 stat_many: ${context} • ${suffix}`, finalCurrent);
92
+ progress.complete(`${GET_MULTIPLE_FILE_INFO_TOOL.title}: ${context} • ${suffix}`, finalCurrent);
92
93
  return result;
93
94
  }
94
95
  catch (error) {
95
- progress.fail(`🕮 stat_many: ${context} • failed`);
96
+ progress.fail(`${GET_MULTIPLE_FILE_INFO_TOOL.title}: ${context} • ${classifyError(error)}`);
96
97
  throw error;
97
98
  }
98
99
  },
@@ -53,15 +53,15 @@ export function registerGetFileInfoTool(server, options = {}) {
53
53
  onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FOUND, args.path),
54
54
  });
55
55
  registerStandardTool(server, GET_FILE_INFO_TOOL, handler, options, {
56
- progressMessage: (args) => `🕮 stat: ${basename(args.path)}`,
56
+ progressMessage: (args) => `${GET_FILE_INFO_TOOL.title}: ${basename(args.path)}`,
57
57
  completionMessage: (args, result) => {
58
58
  const name = basename(args.path);
59
59
  if (result.isError)
60
- return `🕮 stat: ${name} • failed`;
60
+ return `${GET_FILE_INFO_TOOL.title}: ${name} • ${result.errorCode}`;
61
61
  const sc = result.structuredContent;
62
62
  if (!sc.info)
63
- return `🕮 stat: ${name} • failed`;
64
- return `🕮 stat: ${sc.info.name} • ${sc.info.type}, ${formatBytes(sc.info.size)}`;
63
+ return `${GET_FILE_INFO_TOOL.title}: ${name} • failed`;
64
+ return `${GET_FILE_INFO_TOOL.title}: ${sc.info.name} • ${formatBytes(sc.info.size)}`;
65
65
  },
66
66
  });
67
67
  }
@@ -1,5 +1,10 @@
1
1
  import { type McpServer, type RequestTaskStore, type StandardSchemaWithJSON, type ToolTaskHandler } from '@modelcontextprotocol/server';
2
2
  import { type IconInfo, type ToolContext, type ToolContract, type ToolRegistrationOptions, type ToolResult } from './shared.js';
3
+ /**
4
+ * Report an intermediate 'working' status update for the current task.
5
+ * No-op when called outside of a task context.
6
+ */
7
+ export declare function reportTaskStatus(statusMessage: string): Promise<void>;
3
8
  type TaskToolContext = ToolContext & {
4
9
  taskId?: string;
5
10
  taskStore?: RequestTaskStore;
@@ -1,4 +1,4 @@
1
- import {} from '@modelcontextprotocol/server';
1
+ import { isTerminal, RELATED_TASK_META_KEY, } from '@modelcontextprotocol/server';
2
2
  import { AsyncLocalStorage } from 'node:async_hooks';
3
3
  import { channel } from 'node:diagnostics_channel';
4
4
  import { performance } from 'node:perf_hooks';
@@ -7,8 +7,25 @@ import { DEFAULT_TASK_TTL_MS, MAX_CONCURRENT_TASKS, MAX_TASK_TTL_MS, TASK_CANCEL
7
7
  import { ErrorCode, McpError } from '../lib/errors.js';
8
8
  import { Logger } from '../lib/logger.js';
9
9
  import { isRecord } from '../lib/utils.js';
10
- import { buildToolErrorResponse, maybeStripStructuredContentFromResult, resolveToolTaskSupportLevel, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
10
+ import { buildToolErrorResponse, maybeStripStructuredContentFromResult, resolveToolTaskSupportLevel, toToolContext, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
11
11
  const taskContext = new AsyncLocalStorage();
12
+ /**
13
+ * Report an intermediate 'working' status update for the current task.
14
+ * No-op when called outside of a task context.
15
+ */
16
+ export async function reportTaskStatus(statusMessage) {
17
+ const store = taskContext.getStore();
18
+ if (!store)
19
+ return;
20
+ const { taskId, taskStore, ctx, toolName } = store;
21
+ try {
22
+ await taskStore.updateTaskStatus(taskId, 'working', statusMessage);
23
+ await notifyTaskStatusIfPossible(ctx, taskStore, taskId, toolName);
24
+ }
25
+ catch {
26
+ // Best-effort: never fail tool execution for status updates.
27
+ }
28
+ }
12
29
  const TASK_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:tasks');
13
30
  function publishTaskDiagnostics(event) {
14
31
  if (TASK_DIAGNOSTICS_CHANNEL.hasSubscribers) {
@@ -17,44 +34,33 @@ function publishTaskDiagnostics(event) {
17
34
  }
18
35
  // --- Type Guards & Helpers ---
19
36
  function hasTaskToolCapability(server) {
20
- const capabilities = server.server.getCapabilities();
21
- return capabilities.tasks?.requests?.tools?.call !== undefined;
37
+ try {
38
+ const capabilities = server.server.getCapabilities();
39
+ return capabilities.tasks?.requests?.tools?.call !== undefined;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
22
44
  }
23
45
  const TASK_STATUS_NOTIFICATION_METHOD = 'notifications/tasks/status';
24
46
  const TASK_CREATED_NOTIFICATION_METHOD = 'notifications/tasks/created';
25
- function isRequestTaskStore(value) {
26
- return (isRecord(value) &&
27
- typeof value.createTask === 'function' &&
28
- typeof value.getTask === 'function' &&
29
- typeof value.storeTaskResult === 'function' &&
30
- typeof value.getTaskResult === 'function');
31
- }
32
- function hasTaskStoreContext(value) {
33
- return isRecord(value.task) && isRequestTaskStore(value.task.store);
34
- }
35
- function asCreateTaskContext(value) {
36
- if (!hasTaskStoreContext(value)) {
47
+ function assertCreateTaskContext(value) {
48
+ if (!isRecord(value.task) || typeof value.task.store !== 'object') {
37
49
  throw new McpError(ErrorCode.INVALID_INPUT, 'Task store not configured.');
38
50
  }
39
- return value;
40
51
  }
41
- function asTaskRequestContext(value) {
42
- if (!hasTaskStoreContext(value) ||
43
- !isRecord(value.task) ||
52
+ function assertTaskRequestContext(value) {
53
+ if (!isRecord(value.task) ||
54
+ typeof value.task.store !== 'object' ||
44
55
  typeof value.task.id !== 'string' ||
45
56
  value.task.id.length === 0) {
46
57
  throw new McpError(ErrorCode.INVALID_INPUT, 'Task id or store missing.');
47
58
  }
48
- return value;
49
59
  }
50
60
  function toTaskToolContext(ctx) {
51
61
  return {
52
- signal: ctx.mcpReq.signal,
53
- ...(ctx.mcpReq._meta
54
- ? { _meta: ctx.mcpReq._meta }
55
- : {}),
56
- sendNotification: async (notification) => ctx.mcpReq.notify(notification),
57
- ...(hasTaskStoreContext(ctx) ? { taskStore: ctx.task.store } : {}),
62
+ ...toToolContext(ctx),
63
+ taskStore: ctx.task.store,
58
64
  ...(typeof ctx.task.id === 'string' && ctx.task.id.length > 0
59
65
  ? { taskId: ctx.task.id }
60
66
  : {}),
@@ -63,56 +69,11 @@ function toTaskToolContext(ctx) {
63
69
  : {}),
64
70
  };
65
71
  }
66
- const TASK_STATUSES = new Set([
67
- 'submitted',
68
- 'working',
69
- 'input_required',
70
- 'completed',
71
- 'failed',
72
- 'cancelled',
73
- 'unknown',
74
- ]);
75
- function isTaskStatus(value) {
76
- return typeof value === 'string' && TASK_STATUSES.has(value);
77
- }
78
- function normalizeGetTaskResult(value) {
79
- if (!isRecord(value) || typeof value.taskId !== 'string') {
80
- throw new McpError(ErrorCode.INVALID_INPUT, 'Invalid task object.');
81
- }
82
- const status = isTaskStatus(value.status) ? value.status : undefined;
83
- if (!status) {
84
- throw new McpError(ErrorCode.INVALID_INPUT, 'Invalid task status.');
85
- }
86
- const createdAt = typeof value.createdAt === 'string'
87
- ? value.createdAt
88
- : new Date().toISOString();
89
- const lastUpdatedAt = typeof value.lastUpdatedAt === 'string' ? value.lastUpdatedAt : createdAt;
90
- const ttl = typeof value.ttl === 'number' ? value.ttl : null;
91
- const normalized = {
92
- taskId: value.taskId,
93
- status,
94
- ttl,
95
- createdAt,
96
- lastUpdatedAt,
97
- };
98
- if (typeof value.pollInterval === 'number') {
99
- normalized.pollInterval = value.pollInterval;
100
- }
101
- if (typeof value.statusMessage === 'string') {
102
- normalized.statusMessage = value.statusMessage;
103
- }
104
- if (isRecord(value._meta)) {
105
- normalized._meta = value._meta;
106
- }
107
- return normalized;
72
+ function toGetTaskResult(task) {
73
+ return task;
108
74
  }
109
- function normalizeCallToolResult(value) {
110
- if (isRecord(value) &&
111
- Array.isArray(value.content) &&
112
- value.content.every((entry) => isRecord(entry) && typeof entry.type === 'string')) {
113
- return value;
114
- }
115
- throw new McpError(ErrorCode.INVALID_INPUT, 'Invalid stored task result.');
75
+ function toCallToolResult(value) {
76
+ return value;
116
77
  }
117
78
  function getToolResultErrorCode(result) {
118
79
  if (!isRecord(result) || result.isError !== true)
@@ -149,7 +110,7 @@ function attachRelatedTaskMeta(result, taskId) {
149
110
  ...result,
150
111
  _meta: {
151
112
  ...existingMeta,
152
- 'io.modelcontextprotocol/related-task': { taskId },
113
+ [RELATED_TASK_META_KEY]: { taskId },
153
114
  },
154
115
  };
155
116
  }
@@ -182,16 +143,14 @@ function buildTaskStatusNotificationParams(task) {
182
143
  return params;
183
144
  }
184
145
  async function notifyTaskCreatedIfPossible(ctx, taskId, toolName) {
185
- const { sendNotification } = ctx;
186
- if (typeof sendNotification !== 'function')
146
+ if (!ctx.sendNotification)
187
147
  return;
188
- const notify = sendNotification;
189
148
  try {
190
- await notify({
149
+ await ctx.sendNotification({
191
150
  method: TASK_CREATED_NOTIFICATION_METHOD,
192
151
  params: {
193
152
  _meta: {
194
- 'io.modelcontextprotocol/related-task': {
153
+ [RELATED_TASK_META_KEY]: {
195
154
  taskId,
196
155
  },
197
156
  },
@@ -207,14 +166,12 @@ async function notifyTaskCreatedIfPossible(ctx, taskId, toolName) {
207
166
  }
208
167
  }
209
168
  async function notifyTaskStatusIfPossible(ctx, taskStore, taskId, toolName) {
210
- const { sendNotification } = ctx;
211
- if (typeof sendNotification !== 'function')
169
+ if (!ctx.sendNotification)
212
170
  return;
213
- const notify = sendNotification;
214
171
  try {
215
172
  const task = await taskStore.getTask(taskId);
216
- const normalized = await projectCancelledTaskStatus(taskStore, normalizeGetTaskResult(task));
217
- await notify({
173
+ const normalized = await projectCancelledTaskStatus(taskStore, toGetTaskResult(task));
174
+ await ctx.sendNotification({
218
175
  method: TASK_STATUS_NOTIFICATION_METHOD,
219
176
  params: buildTaskStatusNotificationParams(normalized),
220
177
  });
@@ -257,19 +214,26 @@ function withoutStructuredContent(result) {
257
214
  delete stripped['structuredContent'];
258
215
  return stripped;
259
216
  }
260
- const TERMINAL_TASK_STATUSES = new Set([
261
- 'completed',
262
- 'failed',
263
- 'cancelled',
264
- 'unknown',
265
- ]);
217
+ const taskCreationLocks = new WeakMap();
218
+ async function acquireTaskCreationLock(taskStore) {
219
+ const previous = taskCreationLocks.get(taskStore) ?? Promise.resolve();
220
+ let release;
221
+ const next = new Promise((resolve) => {
222
+ release = resolve;
223
+ });
224
+ taskCreationLocks.set(taskStore, previous.catch(() => { }).then(() => next));
225
+ await previous.catch(() => { });
226
+ return () => {
227
+ release();
228
+ };
229
+ }
266
230
  async function isTaskAlreadyTerminal(taskStore, taskId) {
267
231
  try {
268
232
  const task = await taskStore.getTask(taskId);
269
233
  if (!isRecord(task))
270
234
  return false;
271
235
  const { status } = task;
272
- return typeof status === 'string' && TERMINAL_TASK_STATUSES.has(status);
236
+ return typeof status === 'string' && isTerminal(status);
273
237
  }
274
238
  catch {
275
239
  return false;
@@ -288,7 +252,7 @@ async function countActiveTasks(taskStore) {
288
252
  for (const task of tasks) {
289
253
  if (!isRecord(task) || typeof task.status !== 'string')
290
254
  continue;
291
- if (!TERMINAL_TASK_STATUSES.has(task.status)) {
255
+ if (!isTerminal(task.status)) {
292
256
  active += 1;
293
257
  }
294
258
  }
@@ -344,7 +308,7 @@ async function runTaskInBackground(run, args, ctx, taskStore, taskId, toolName,
344
308
  let taskStatuses;
345
309
  let result;
346
310
  try {
347
- const rawResult = await taskContext.run({ taskId, toolName, startTime: start }, () => run(args, taskExtra));
311
+ const rawResult = await taskContext.run({ taskId, toolName, startTime: start, taskStore, ctx: taskExtra }, () => run(args, taskExtra));
348
312
  taskStatuses = resolveTaskResultStatuses(rawResult);
349
313
  result = isErrorResult(rawResult)
350
314
  ? withoutStructuredContent(rawResult)
@@ -379,10 +343,9 @@ async function runTaskInBackground(run, args, ctx, taskStore, taskId, toolName,
379
343
  lastUpdatedAt: new Date().toISOString(),
380
344
  statusMessage: 'Internal system error while storing result',
381
345
  };
382
- const { sendNotification } = ctx;
383
- if (typeof sendNotification === 'function') {
346
+ if (ctx.sendNotification) {
384
347
  try {
385
- await sendNotification({
348
+ await ctx.sendNotification({
386
349
  method: TASK_STATUS_NOTIFICATION_METHOD,
387
350
  params: buildTaskStatusNotificationParams(syntheticTask),
388
351
  });
@@ -438,18 +401,26 @@ export function createToolTaskHandler(run, options) {
438
401
  else {
439
402
  [args, serverCtx] = params;
440
403
  }
441
- const ctx = toTaskToolContext(asCreateTaskContext(serverCtx));
404
+ assertCreateTaskContext(serverCtx);
405
+ const ctx = toTaskToolContext(serverCtx);
442
406
  if (options?.guard && !options.guard()) {
443
407
  throw new McpError(ErrorCode.INVALID_INPUT, 'Client not initialized; wait for notifications/initialized');
444
408
  }
445
409
  const taskStore = getTaskStore(ctx);
446
- if ((await countActiveTasks(taskStore)) >= MAX_CONCURRENT_TASKS) {
447
- throw new McpError(ErrorCode.INVALID_INPUT, `Too many active tasks (limit: ${String(MAX_CONCURRENT_TASKS)}).`);
410
+ const releaseCreationLock = await acquireTaskCreationLock(taskStore);
411
+ let task;
412
+ try {
413
+ if ((await countActiveTasks(taskStore)) >= MAX_CONCURRENT_TASKS) {
414
+ throw new McpError(ErrorCode.INVALID_INPUT, `Too many active tasks (limit: ${String(MAX_CONCURRENT_TASKS)}).`);
415
+ }
416
+ task = await taskStore.createTask({
417
+ ttl: resolveRequestedTaskTtl(ctx.taskRequestedTtl),
418
+ pollInterval: options?.pollIntervalMs ?? TASK_POLL_INTERVAL_MS,
419
+ });
420
+ }
421
+ finally {
422
+ releaseCreationLock();
448
423
  }
449
- const task = await taskStore.createTask({
450
- ttl: resolveRequestedTaskTtl(ctx.taskRequestedTtl),
451
- pollInterval: options?.pollIntervalMs ?? TASK_POLL_INTERVAL_MS,
452
- });
453
424
  const toolLabel = options?.toolName ?? 'tool';
454
425
  try {
455
426
  await taskStore.updateTaskStatus(task.taskId, 'working', `${toolLabel}: starting`);
@@ -473,26 +444,25 @@ export function createToolTaskHandler(run, options) {
473
444
  void runTaskInBackground(run, args, taskExtra, taskStore, task.taskId, options?.toolName, options?.cancelPollMs);
474
445
  return {
475
446
  task,
476
- _meta: {
477
- 'io.modelcontextprotocol/model-immediate-response': `${toolLabel} task created — poll tasks/get for progress.`,
478
- },
479
447
  };
480
448
  });
481
449
  const getTask = (async (...params) => {
482
450
  const serverCtx = params.length === 1 ? params[0] : params[1];
483
- const ctx = toTaskToolContext(asTaskRequestContext(serverCtx));
451
+ assertTaskRequestContext(serverCtx);
452
+ const ctx = toTaskToolContext(serverCtx);
484
453
  const taskStore = getTaskStore(ctx);
485
454
  const taskId = getTaskId(ctx);
486
455
  const task = await taskStore.getTask(taskId);
487
- return projectCancelledTaskStatus(taskStore, normalizeGetTaskResult(task));
456
+ return projectCancelledTaskStatus(taskStore, toGetTaskResult(task));
488
457
  });
489
458
  const getTaskResult = (async (...params) => {
490
459
  const serverCtx = params.length === 1 ? params[0] : params[1];
491
- const ctx = toTaskToolContext(asTaskRequestContext(serverCtx));
460
+ assertTaskRequestContext(serverCtx);
461
+ const ctx = toTaskToolContext(serverCtx);
492
462
  const taskStore = getTaskStore(ctx);
493
463
  const taskId = getTaskId(ctx);
494
464
  const result = await taskStore.getTaskResult(taskId);
495
- return attachRelatedTaskMeta(normalizeCallToolResult(result), taskId);
465
+ return attachRelatedTaskMeta(toCallToolResult(result), taskId);
496
466
  });
497
467
  return {
498
468
  createTask,
@@ -1,6 +1,6 @@
1
1
  import { basename } from 'node:path';
2
2
  import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
3
- import { ErrorCode } from '../lib/errors.js';
3
+ import { classifyError, ErrorCode } from '../lib/errors.js';
4
4
  import { formatTreeAscii, treeDirectory, } from '../lib/file-operations/metadata.js';
5
5
  import { TreeInputSchema, TreeOutputSchema } from '../schemas.js';
6
6
  import { DIRECTORY_ICONS } from './icons.js';
@@ -56,7 +56,7 @@ export function registerTreeTool(server, options = {}) {
56
56
  notifyProgress(ctx, {
57
57
  current: 0,
58
58
  total: knownTotal,
59
- message: `≣ tree: ${context}`,
59
+ message: `${TREE_TOOL.title}: ${context}`,
60
60
  });
61
61
  const baseReporter = createProgressReporter(ctx);
62
62
  const onProgress = (progress) => {
@@ -66,7 +66,7 @@ export function registerTreeTool(server, options = {}) {
66
66
  baseReporter({
67
67
  current,
68
68
  total: knownTotal,
69
- message: `≣ tree: ${context} [${current} entries]`,
69
+ message: `${TREE_TOOL.title}: ${context} [${current} entries]`,
70
70
  });
71
71
  };
72
72
  try {
@@ -81,7 +81,7 @@ export function registerTreeTool(server, options = {}) {
81
81
  notifyProgress(ctx, {
82
82
  current: finalCurrent,
83
83
  total: finalCurrent,
84
- message: `≣ tree: ${context} • ${suffix}`,
84
+ message: `${TREE_TOOL.title}: ${context} • ${suffix}`,
85
85
  });
86
86
  return result;
87
87
  }
@@ -90,7 +90,7 @@ export function registerTreeTool(server, options = {}) {
90
90
  notifyProgress(ctx, {
91
91
  current: finalCurrent,
92
92
  total: finalCurrent,
93
- message: `≣ tree: ${context} • failed`,
93
+ message: `${TREE_TOOL.title}: ${context} • ${classifyError(error)}`,
94
94
  });
95
95
  throw error;
96
96
  }
@@ -48,13 +48,13 @@ export function registerWriteFileTool(server, options = {}) {
48
48
  onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.path),
49
49
  });
50
50
  registerStandardTool(server, WRITE_FILE_TOOL, handler, options, {
51
- progressMessage: (args) => `🛠 write: ${basename(args.path)}`,
51
+ progressMessage: (args) => `${WRITE_FILE_TOOL.title}: ${basename(args.path)}`,
52
52
  completionMessage: (args, result) => {
53
53
  const name = basename(args.path);
54
54
  if (result.isError)
55
- return `🛠 write: ${name} • failed`;
55
+ return `${WRITE_FILE_TOOL.title}: ${name} • ${result.errorCode}`;
56
56
  const sc = result.structuredContent;
57
- return `🛠 write: ${name} • ${formatBytes(sc.bytesWritten ?? 0)}`;
57
+ return `${WRITE_FILE_TOOL.title}: ${name} • ${formatBytes(sc.bytesWritten ?? 0)}`;
58
58
  },
59
59
  });
60
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/filesystem-mcp",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "mcpName": "io.github.j0hanz/filesystem-mcp",
5
5
  "description": "Secure filesystem MCP server for reading, writing, searching, diffing, and patching files.",
6
6
  "author": "j0hanz",