@j0hanz/filesystem-mcp 1.17.1 → 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.
@@ -7,7 +7,9 @@ export declare const MAX_CONCURRENT_TASKS: number;
7
7
  export declare const TASK_CANCEL_POLL_MS = 2000;
8
8
  export declare function getInitHandshakeTimeoutMs(): number;
9
9
  export declare const INIT_TIMEOUT_CLOSE: boolean;
10
- export declare const TASK_POLL_INTERVAL_MS = 100;
10
+ export declare const TASK_POLL_INTERVAL_MS = 500;
11
+ /** How long cancelled-task results are retained before lazy eviction. */
12
+ export declare const CANCELLED_RESULT_TTL_MS: number;
11
13
  export declare const PARALLEL_CONCURRENCY: number;
12
14
  export declare const MAX_SEARCHABLE_FILE_SIZE: number;
13
15
  export declare const MAX_TEXT_FILE_SIZE: number;
@@ -80,7 +80,9 @@ export function getInitHandshakeTimeoutMs() {
80
80
  return parseEnvInt('FS_INIT_HANDSHAKE_TIMEOUT_MS', 30_000, 1_000, 300_000);
81
81
  }
82
82
  export const INIT_TIMEOUT_CLOSE = parseTrueEnvFlag(process.env['FS_INIT_TIMEOUT_CLOSE']);
83
- export const TASK_POLL_INTERVAL_MS = 100;
83
+ export const TASK_POLL_INTERVAL_MS = 500;
84
+ /** How long cancelled-task results are retained before lazy eviction. */
85
+ export const CANCELLED_RESULT_TTL_MS = 2 * 60 * 1_000; // 2 minutes
84
86
  // Auto-tuned parallelism based on CPU cores (no env override)
85
87
  const BYTES_PER_PARALLEL_TASK = 64 * MIB;
86
88
  const BYTES_PER_SEARCH_WORKER = 128 * MIB;
@@ -22,6 +22,7 @@ export declare class McpError extends Error {
22
22
  static accessDenied(message: string, path?: string, details?: Record<string, unknown>, cause?: unknown): McpError;
23
23
  static timeout(message: string, path?: string, details?: Record<string, unknown>, cause?: unknown): McpError;
24
24
  }
25
+ export declare function classifyError(error: unknown): ErrorCode;
25
26
  export declare function createDetailedError(error: unknown, path?: string, additionalDetails?: Record<string, unknown>): DetailedError;
26
27
  export declare function formatDetailedError(error: DetailedError): string;
27
28
  export declare function getSuggestion(code: ErrorCode): string | undefined;
@@ -236,7 +236,7 @@ function classifyMessageError(error) {
236
236
  }
237
237
  return undefined;
238
238
  }
239
- function classifyError(error) {
239
+ export function classifyError(error) {
240
240
  let timeoutCode;
241
241
  let fallbackCode;
242
242
  const terminalCode = walkErrorChain(error, (candidate) => {
package/dist/prompts.js CHANGED
@@ -57,6 +57,10 @@ export function registerGetHelpPrompt(server, instructions, iconInfo) {
57
57
  content: {
58
58
  type: 'text',
59
59
  text,
60
+ annotations: {
61
+ audience: ['assistant'],
62
+ priority: 1,
63
+ },
60
64
  },
61
65
  },
62
66
  ],
@@ -81,6 +85,10 @@ export function registerCompareFilesPrompt(server, iconInfo) {
81
85
  content: {
82
86
  type: 'text',
83
87
  text: `Compare files and explain differences.\n\n1. Call \`diff_files\` with:\n - original: ${original}\n - modified: ${modified}\n2. Summarize: additions, deletions, and semantic changes.\n3. Flag any potential issues (conflicts, regressions, breaking changes).`,
88
+ annotations: {
89
+ audience: ['assistant'],
90
+ priority: 1,
91
+ },
84
92
  },
85
93
  },
86
94
  ],
@@ -103,6 +111,10 @@ export function registerAnalyzePathPrompt(server, iconInfo) {
103
111
  content: {
104
112
  type: 'text',
105
113
  text: `Analyze the path: ${targetPath}\n\n1. Call \`stat\` to determine if it is a file or directory.\n2. If file: call \`read\` with \`includeHash: true\` and summarize contents.\n3. If directory: call \`tree\` (maxDepth: 3) and \`ls\` to summarize structure.\n4. Report: type, size, permissions, key observations.`,
114
+ annotations: {
115
+ audience: ['assistant'],
116
+ priority: 1,
117
+ },
106
118
  },
107
119
  },
108
120
  ],
@@ -138,6 +150,10 @@ export function registerGetToolHelpPrompt(server, iconInfo) {
138
150
  type: 'text',
139
151
  text: `Use the embedded contract for \`${toolName}\` as the authoritative reference. ` +
140
152
  'Summarize when to use it, its key constraints, and the safest next action.',
153
+ annotations: {
154
+ audience: ['assistant'],
155
+ priority: 1,
156
+ },
141
157
  },
142
158
  },
143
159
  {
@@ -149,6 +165,10 @@ export function registerGetToolHelpPrompt(server, iconInfo) {
149
165
  mimeType: 'text/markdown',
150
166
  text: toolInfo,
151
167
  },
168
+ annotations: {
169
+ audience: ['assistant'],
170
+ priority: 1,
171
+ },
152
172
  },
153
173
  },
154
174
  ],
@@ -1,5 +1,5 @@
1
1
  import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
2
- import { InMemoryTaskMessageQueue, isInitializeRequest, LATEST_PROTOCOL_VERSION, localhostAllowedHostnames, McpServer, StdioServerTransport, SUPPORTED_PROTOCOL_VERSIONS, validateHostHeader, } from '@modelcontextprotocol/server';
2
+ import { InMemoryTaskMessageQueue, isInitializeRequest, localhostAllowedHostnames, McpServer, StdioServerTransport, validateHostHeader, } from '@modelcontextprotocol/server';
3
3
  import { createHash, randomUUID, timingSafeEqual } from 'node:crypto';
4
4
  import { channel } from 'node:diagnostics_channel';
5
5
  import { readFile } from 'node:fs/promises';
@@ -17,6 +17,7 @@ import { registerInstructionResource, registerMetricsResource, registerResultRes
17
17
  import { buildServerInstructions } from '../resources/generated-instructions.js';
18
18
  import { registerAllTools } from '../tools.js';
19
19
  import { withDefaultIcons } from '../tools/shared.js';
20
+ import { InMemoryEventStore } from './event-store.js';
20
21
  import { RootsManager } from './roots-manager.js';
21
22
  import { createTaskStore } from './task-store.js';
22
23
  function buildServerCapabilities(options = {}) {
@@ -221,7 +222,7 @@ async function readRequestBody(req) {
221
222
  req.on('error', reject);
222
223
  });
223
224
  }
224
- async function createHttpSession(options, sessions, negotiatedProtocolVersion) {
225
+ async function createHttpSession(options, sessions, eventStore) {
225
226
  const mcpServer = await createServer(options);
226
227
  const rootsManager = getRootsManager(mcpServer);
227
228
  rootsManager.registerHandlers(mcpServer);
@@ -235,6 +236,7 @@ async function createHttpSession(options, sessions, negotiatedProtocolVersion) {
235
236
  if (sessionId) {
236
237
  sessions.delete(sessionId);
237
238
  activeServers.delete(sessionId);
239
+ eventStore.delete(sessionId);
238
240
  }
239
241
  rootsManager.destroy();
240
242
  };
@@ -244,12 +246,12 @@ async function createHttpSession(options, sessions, negotiatedProtocolVersion) {
244
246
  };
245
247
  const transport = new NodeStreamableHTTPServerTransport({
246
248
  sessionIdGenerator: () => randomUUID(),
249
+ eventStore,
247
250
  onsessioninitialized: (sessionId) => {
248
251
  sessions.set(sessionId, {
249
252
  server: mcpServer,
250
253
  rootsManager,
251
254
  transport,
252
- negotiatedProtocolVersion,
253
255
  createdAt: Date.now(),
254
256
  cleanup,
255
257
  close,
@@ -267,7 +269,6 @@ async function createHttpSession(options, sessions, negotiatedProtocolVersion) {
267
269
  server: mcpServer,
268
270
  rootsManager,
269
271
  transport,
270
- negotiatedProtocolVersion,
271
272
  createdAt: Date.now(),
272
273
  cleanup,
273
274
  close,
@@ -318,29 +319,6 @@ function getSessionId(req) {
318
319
  ? rawSessionId
319
320
  : undefined;
320
321
  }
321
- function getProtocolVersionHeader(req) {
322
- const rawProtocolVersion = req.headers['mcp-protocol-version'];
323
- return typeof rawProtocolVersion === 'string'
324
- ? rawProtocolVersion
325
- : undefined;
326
- }
327
- function resolveNegotiatedProtocolVersion(requestedVersion) {
328
- return SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)
329
- ? requestedVersion
330
- : LATEST_PROTOCOL_VERSION;
331
- }
332
- function ensureSessionProtocolVersion(req, res, session) {
333
- const protocolVersion = getProtocolVersionHeader(req);
334
- if (!protocolVersion) {
335
- sendJsonRpcError(res, 400, JSON_RPC_SERVER_ERROR, 'Bad Request: Missing MCP-Protocol-Version header');
336
- return false;
337
- }
338
- if (protocolVersion !== session.negotiatedProtocolVersion) {
339
- sendJsonRpcError(res, 400, JSON_RPC_SERVER_ERROR, `Bad Request: MCP-Protocol-Version must match negotiated version ${session.negotiatedProtocolVersion}`);
340
- return false;
341
- }
342
- return true;
343
- }
344
322
  function isAuthorizedBearer(apiKey, authHeader) {
345
323
  const bearerPrefix = 'Bearer ';
346
324
  if (typeof authHeader !== 'string' || !authHeader.startsWith(bearerPrefix)) {
@@ -460,6 +438,7 @@ function handleHttpRequestError(error, res) {
460
438
  }
461
439
  export async function startHttpServer(port, options) {
462
440
  const sessions = new Map();
441
+ const eventStore = new InMemoryEventStore();
463
442
  const httpHost = process.env['FILESYSTEM_MCP_HTTP_HOST'] ?? '127.0.0.1';
464
443
  assertHttpBindingSecurity(httpHost);
465
444
  let closingSessions;
@@ -469,6 +448,7 @@ export async function startHttpServer(port, options) {
469
448
  closingSessions = (async () => {
470
449
  const activeSessions = [...sessions.values()];
471
450
  sessions.clear();
451
+ eventStore.clear();
472
452
  await Promise.allSettled(activeSessions.map((session) => session.close()));
473
453
  })();
474
454
  await closingSessions;
@@ -480,10 +460,6 @@ export async function startHttpServer(port, options) {
480
460
  discardRequestBody(req);
481
461
  return;
482
462
  }
483
- if (!ensureSessionProtocolVersion(req, res, session)) {
484
- discardRequestBody(req);
485
- return;
486
- }
487
463
  const body = await readRequestBody(req);
488
464
  await handleSessionTransportRequest(session, req, res, body);
489
465
  return;
@@ -495,7 +471,7 @@ export async function startHttpServer(port, options) {
495
471
  sendJsonRpcError(res, 503, JSON_RPC_SERVER_ERROR, 'Too many sessions');
496
472
  return;
497
473
  }
498
- const session = await createHttpSession(options, sessions, resolveNegotiatedProtocolVersion(body.params.protocolVersion));
474
+ const session = await createHttpSession(options, sessions, eventStore);
499
475
  await handleSessionTransportRequest(session, req, res, body);
500
476
  return;
501
477
  }
@@ -510,8 +486,6 @@ export async function startHttpServer(port, options) {
510
486
  const session = getSessionOrRespondNotFound(sessions, sessionId, res);
511
487
  if (!session)
512
488
  return;
513
- if (!ensureSessionProtocolVersion(req, res, session))
514
- return;
515
489
  await handleSessionTransportRequest(session, req, res);
516
490
  }
517
491
  async function dispatchMcpMethod(method, req, res, sessionId) {
@@ -0,0 +1,18 @@
1
+ import type { JSONRPCMessage } from '@modelcontextprotocol/server';
2
+ export declare class InMemoryEventStore {
3
+ private streams;
4
+ private eventIdToStreamId;
5
+ storeEvent(streamId: string, message: JSONRPCMessage): Promise<string>;
6
+ getStreamIdForEventId(eventId: string): Promise<string | undefined>;
7
+ replayEventsAfter(lastEventId: string, callbacks: {
8
+ send: (eventId: string, message: JSONRPCMessage) => Promise<void>;
9
+ }): Promise<string>;
10
+ /**
11
+ * Cleans up all events for a given streamId.
12
+ */
13
+ delete(streamId: string): void;
14
+ /**
15
+ * Cleans up all streams.
16
+ */
17
+ clear(): void;
18
+ }
@@ -0,0 +1,71 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ const MAX_EVENTS_PER_STREAM = 1000;
3
+ export class InMemoryEventStore {
4
+ // Map of streamId -> StoredEvent[]
5
+ streams = new Map();
6
+ // Map of eventId -> streamId for fast lookup
7
+ eventIdToStreamId = new Map();
8
+ storeEvent(streamId, message) {
9
+ const eventId = randomUUID();
10
+ let stream = this.streams.get(streamId);
11
+ if (!stream) {
12
+ stream = [];
13
+ this.streams.set(streamId, stream);
14
+ }
15
+ // Add new event
16
+ stream.push({ id: eventId, message });
17
+ this.eventIdToStreamId.set(eventId, streamId);
18
+ // Enforce limits
19
+ if (stream.length > MAX_EVENTS_PER_STREAM) {
20
+ const removed = stream.shift();
21
+ if (removed) {
22
+ this.eventIdToStreamId.delete(removed.id);
23
+ }
24
+ }
25
+ return Promise.resolve(eventId);
26
+ }
27
+ getStreamIdForEventId(eventId) {
28
+ return Promise.resolve(this.eventIdToStreamId.get(eventId));
29
+ }
30
+ async replayEventsAfter(lastEventId, callbacks) {
31
+ const streamId = this.eventIdToStreamId.get(lastEventId);
32
+ if (!streamId) {
33
+ throw new Error(`Event ID ${lastEventId} not found or expired`);
34
+ }
35
+ const stream = this.streams.get(streamId);
36
+ if (!stream) {
37
+ throw new Error(`Stream ${streamId} not found`);
38
+ }
39
+ const eventIndex = stream.findIndex((e) => e.id === lastEventId);
40
+ if (eventIndex === -1) {
41
+ throw new Error(`Event ID ${lastEventId} not found in stream ${streamId}`);
42
+ }
43
+ // Replay all events after the found index
44
+ for (let i = eventIndex + 1; i < stream.length; i++) {
45
+ const event = stream[i];
46
+ if (event) {
47
+ await callbacks.send(event.id, event.message);
48
+ }
49
+ }
50
+ return streamId;
51
+ }
52
+ /**
53
+ * Cleans up all events for a given streamId.
54
+ */
55
+ delete(streamId) {
56
+ const stream = this.streams.get(streamId);
57
+ if (stream) {
58
+ for (const event of stream) {
59
+ this.eventIdToStreamId.delete(event.id);
60
+ }
61
+ this.streams.delete(streamId);
62
+ }
63
+ }
64
+ /**
65
+ * Cleans up all streams.
66
+ */
67
+ clear() {
68
+ this.streams.clear();
69
+ this.eventIdToStreamId.clear();
70
+ }
71
+ }
@@ -1,6 +1,7 @@
1
1
  import { InMemoryTaskStore, type Result, type Task } from '@modelcontextprotocol/server';
2
2
  export declare class ResultAwareInMemoryTaskStore extends InMemoryTaskStore {
3
3
  private readonly cancelledResults;
4
+ private evictExpired;
4
5
  getTaskResult(taskId: string, sessionId?: string): Promise<Result>;
5
6
  storeTaskResult(taskId: string, status: 'completed' | 'failed', result: Result, sessionId?: string): Promise<void>;
6
7
  updateTaskStatus(taskId: string, status: Task['status'], statusMessage?: string, sessionId?: string): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { InMemoryTaskStore, } from '@modelcontextprotocol/server';
2
+ import { CANCELLED_RESULT_TTL_MS } from '../lib/constants.js';
2
3
  import { ErrorCode } from '../lib/errors.js';
3
4
  const DEFAULT_CANCELLED_STATUS_MESSAGE = 'Client cancelled task execution.';
4
5
  function getTaskKey(taskId, sessionId) {
@@ -18,7 +19,16 @@ function buildCancelledTaskResult(statusMessage) {
18
19
  }
19
20
  export class ResultAwareInMemoryTaskStore extends InMemoryTaskStore {
20
21
  cancelledResults = new Map();
22
+ evictExpired() {
23
+ const now = Date.now();
24
+ for (const [key, entry] of this.cancelledResults) {
25
+ if (now - entry.createdAt > CANCELLED_RESULT_TTL_MS) {
26
+ this.cancelledResults.delete(key);
27
+ }
28
+ }
29
+ }
21
30
  async getTaskResult(taskId, sessionId) {
31
+ this.evictExpired();
22
32
  try {
23
33
  return await super.getTaskResult(taskId, sessionId);
24
34
  }
@@ -30,9 +40,9 @@ export class ResultAwareInMemoryTaskStore extends InMemoryTaskStore {
30
40
  const key = getTaskKey(taskId, sessionId);
31
41
  const existing = this.cancelledResults.get(key);
32
42
  if (existing)
33
- return existing;
43
+ return existing.result;
34
44
  const result = buildCancelledTaskResult(task.statusMessage);
35
- this.cancelledResults.set(key, result);
45
+ this.cancelledResults.set(key, { result, createdAt: Date.now() });
36
46
  return result;
37
47
  }
38
48
  }
@@ -48,15 +58,23 @@ export class ResultAwareInMemoryTaskStore extends InMemoryTaskStore {
48
58
  if (task?.status !== 'cancelled') {
49
59
  throw error;
50
60
  }
51
- this.cancelledResults.set(getTaskKey(taskId, sessionId), this.cancelledResults.get(getTaskKey(taskId, sessionId)) ?? result);
61
+ const key = getTaskKey(taskId, sessionId);
62
+ const existing = this.cancelledResults.get(key);
63
+ this.cancelledResults.set(key, {
64
+ result: existing?.result ?? result,
65
+ createdAt: existing?.createdAt ?? Date.now(),
66
+ });
52
67
  }
53
68
  }
54
69
  async updateTaskStatus(taskId, status, statusMessage, sessionId) {
55
70
  await super.updateTaskStatus(taskId, status, statusMessage, sessionId);
56
71
  const key = getTaskKey(taskId, sessionId);
57
72
  if (status === 'cancelled') {
58
- this.cancelledResults.set(key, this.cancelledResults.get(key) ??
59
- buildCancelledTaskResult(statusMessage));
73
+ const existing = this.cancelledResults.get(key);
74
+ this.cancelledResults.set(key, {
75
+ result: existing?.result ?? buildCancelledTaskResult(statusMessage),
76
+ createdAt: existing?.createdAt ?? Date.now(),
77
+ });
60
78
  return;
61
79
  }
62
80
  if (status === 'completed' || status === 'failed') {
@@ -213,21 +213,23 @@ export function registerApplyPatchTool(server, options = {}) {
213
213
  registerStandardTool(server, APPLY_PATCH_TOOL, handler, options, {
214
214
  progressMessage: (args) => {
215
215
  const name = basename(args.path);
216
- return args.dryRun ? `🛠 patch: ${name} [dry run]` : `🛠 patch: ${name}`;
216
+ return args.dryRun
217
+ ? `${APPLY_PATCH_TOOL.title}: ${name} [dry run]`
218
+ : `${APPLY_PATCH_TOOL.title}: ${name}`;
217
219
  },
218
220
  completionMessage: (args, result) => {
219
221
  const name = basename(args.path);
220
222
  if (result.isError)
221
- return `🛠 patch: ${name} • failed`;
223
+ return `${APPLY_PATCH_TOOL.title}: ${name} • ${result.errorCode}`;
222
224
  const sc = result.structuredContent;
223
225
  if (!sc.ok)
224
- return `🛠 patch: ${name} • failed`;
226
+ return `${APPLY_PATCH_TOOL.title}: ${name} • failed`;
225
227
  const added = sc.linesAdded ?? 0;
226
228
  const removed = sc.linesRemoved ?? 0;
227
229
  const dry = args.dryRun ? 'dry run ' : '';
228
230
  if (added > 0 || removed > 0)
229
- return `🛠 patch: ${name} • ${dry} +${added} -${removed}`;
230
- return `🛠 patch: ${name} • ${dry}no changes`;
231
+ return `${APPLY_PATCH_TOOL.title}: ${name} • ${dry} +${added} -${removed}`;
232
+ return `${APPLY_PATCH_TOOL.title}: ${name} • ${dry}no changes`;
231
233
  },
232
234
  });
233
235
  }
@@ -5,7 +5,7 @@ import { basename, relative, win32 } from 'node:path';
5
5
  import { pipeline } from 'node:stream/promises';
6
6
  import { assertNotAborted, withAbort } from '../lib/abort.js';
7
7
  import { PARALLEL_CONCURRENCY } from '../lib/constants.js';
8
- import { ErrorCode } from '../lib/errors.js';
8
+ import { classifyError, ErrorCode } from '../lib/errors.js';
9
9
  import { isIgnoredByGitignore, loadRootGitignore, } from '../lib/file-operations/core.js';
10
10
  import { globEntries } from '../lib/file-operations/traversal.js';
11
11
  import { validateExistingPath } from '../lib/paths.js';
@@ -153,12 +153,12 @@ export function registerCalculateHashTool(server, options = {}) {
153
153
  context: { path: args.path },
154
154
  run: async (signal) => {
155
155
  const baseName = basename(args.path);
156
- const progress = createToolProgressSession(ctx, `🕮 hash: ${baseName}`);
156
+ const progress = createToolProgressSession(ctx, `${CALCULATE_HASH_TOOL.title}: ${baseName}`);
157
157
  const progressWithMessage = ({ current, total, }) => {
158
158
  progress.update({
159
159
  current,
160
160
  ...(total !== undefined ? { total } : {}),
161
- message: `🕮 hash: ${baseName} [${current} files]`,
161
+ message: `${CALCULATE_HASH_TOOL.title}: ${baseName} [${current} files]`,
162
162
  });
163
163
  };
164
164
  try {
@@ -173,11 +173,11 @@ export function registerCalculateHashTool(server, options = {}) {
173
173
  else {
174
174
  suffix = `${(sc.hash ?? '').slice(0, 8)}…`;
175
175
  }
176
- progress.complete(`🕮 hash: ${baseName} • ${suffix}`, finalCurrent);
176
+ progress.complete(`${CALCULATE_HASH_TOOL.title}: ${baseName} • ${suffix}`, finalCurrent);
177
177
  return result;
178
178
  }
179
179
  catch (error) {
180
- progress.fail(`🕮 hash: ${baseName} • failed`);
180
+ progress.fail(`${CALCULATE_HASH_TOOL.title}: ${baseName} • ${classifyError(error)}`);
181
181
  throw error;
182
182
  }
183
183
  },
@@ -48,22 +48,21 @@ export function registerCreateDirectoryTool(server, options = {}) {
48
48
  registerStandardTool(server, CREATE_DIRECTORY_TOOL, handler, options, {
49
49
  progressMessage: (args) => {
50
50
  if (args.path && !args.paths?.length) {
51
- return `🛠 mkdir: ${basename(args.path)}`;
51
+ return `${CREATE_DIRECTORY_TOOL.title}: ${basename(args.path)}`;
52
52
  }
53
53
  const count = (args.path ? 1 : 0) + (args.paths?.length ?? 0);
54
- return `🛠 mkdir: ${count} directories`;
54
+ return `${CREATE_DIRECTORY_TOOL.title}: ${count} directories`;
55
55
  },
56
56
  completionMessage: (args, result) => {
57
57
  if (args.path && !args.paths?.length) {
58
58
  const name = basename(args.path);
59
59
  if (result.isError)
60
- return `🛠 mkdir: ${name} • failed`;
61
- return `🛠 mkdir: ${name}`;
60
+ return `${CREATE_DIRECTORY_TOOL.title}: ${name} • ${result.errorCode}`;
62
61
  }
63
62
  const count = (args.path ? 1 : 0) + (args.paths?.length ?? 0);
64
63
  if (result.isError)
65
- return `🛠 mkdir: ${count} directories • failed`;
66
- return `🛠 mkdir: ${count} directories`;
64
+ return `${CREATE_DIRECTORY_TOOL.title}: ${count} directories • ${result.errorCode}`;
65
+ return `${CREATE_DIRECTORY_TOOL.title}: ${count} directories`;
67
66
  },
68
67
  });
69
68
  }
@@ -90,12 +90,12 @@ export function registerDeleteFileTool(server, options = {}) {
90
90
  },
91
91
  });
92
92
  registerStandardTool(server, DELETE_FILE_TOOL, handler, options, {
93
- progressMessage: (args) => `🛠 rm: ${basename(args.path)}`,
93
+ progressMessage: (args) => `${DELETE_FILE_TOOL.title}: ${basename(args.path)}`,
94
94
  completionMessage: (args, result) => {
95
95
  const name = basename(args.path);
96
96
  if (result.isError)
97
- return `🛠 rm: ${name} • failed`;
98
- return `🛠 rm: ${name}`;
97
+ return `${DELETE_FILE_TOOL.title}: ${name} • ${result.errorCode}`;
98
+ return `${DELETE_FILE_TOOL.title}: ${name}`;
99
99
  },
100
100
  });
101
101
  }
@@ -116,21 +116,21 @@ export function registerDiffFilesTool(server, options = {}) {
116
116
  progressMessage: (args) => {
117
117
  const n1 = basename(args.original);
118
118
  const n2 = basename(args.modified);
119
- return `🕮 diff: ${n1} ⟷ ${n2}`;
119
+ return `${DIFF_FILES_TOOL.title}: ${n1} ⟷ ${n2}`;
120
120
  },
121
121
  completionMessage: (args, result) => {
122
122
  const n1 = basename(args.original);
123
123
  const n2 = basename(args.modified);
124
124
  if (result.isError)
125
- return `🕮 diff: ${n1} ⟷ ${n2} • failed`;
125
+ return `${DIFF_FILES_TOOL.title}: ${n1} ⟷ ${n2} • ${result.errorCode}`;
126
126
  const sc = result.structuredContent;
127
127
  if (sc.isIdentical)
128
- return `🕮 diff: ${n1} ⟷ ${n2} • identical`;
128
+ return `${DIFF_FILES_TOOL.title}: ${n1} ⟷ ${n2} • identical`;
129
129
  const added = sc.linesAdded ?? 0;
130
130
  const removed = sc.linesRemoved ?? 0;
131
131
  if (added > 0 || removed > 0)
132
- return `🕮 diff: ${n1} ⟷ ${n2} • +${added} -${removed}`;
133
- return `🕮 diff: ${n1} ⟷ ${n2}`;
132
+ return `${DIFF_FILES_TOOL.title}: ${n1} ⟷ ${n2} • +${added} -${removed}`;
133
+ return `${DIFF_FILES_TOOL.title}: ${n1} ⟷ ${n2}`;
134
134
  },
135
135
  });
136
136
  }
@@ -178,22 +178,22 @@ async function loadEditableFile(requestedPath, signal) {
178
178
  function buildEditProgressMessage(args) {
179
179
  const name = basename(args.path);
180
180
  const tag = args.dryRun ? ' [dry run]' : '';
181
- return `🛠 edit: ${name}${tag}`;
181
+ return `${EDIT_FILE_TOOL.title}: ${name}${tag}`;
182
182
  }
183
183
  function buildEditCompletionMessage(args, result) {
184
184
  const name = basename(args.path);
185
185
  if (result.isError)
186
- return `🛠 edit: ${name} • failed`;
186
+ return `${EDIT_FILE_TOOL.title}: ${name} • ${result.errorCode}`;
187
187
  const { structuredContent } = result;
188
188
  if (!structuredContent.ok)
189
- return `🛠 edit: ${name} • failed`;
189
+ return `${EDIT_FILE_TOOL.title}: ${name} • failed`;
190
190
  const applied = structuredContent.appliedEdits ?? 0;
191
191
  if (applied === 0)
192
- return `🛠 edit: ${name} • no changes`;
192
+ return `${EDIT_FILE_TOOL.title}: ${name} • no changes`;
193
193
  const added = structuredContent.linesAdded ?? 0;
194
194
  const removed = structuredContent.linesRemoved ?? 0;
195
195
  const dry = args.dryRun ? 'dry run ' : '';
196
- return `🛠 edit: ${name} • ${dry} +${added} -${removed}`;
196
+ return `${EDIT_FILE_TOOL.title}: ${name} • ${dry} +${added} -${removed}`;
197
197
  }
198
198
  async function applyEdits(content, edits, ignoreWhitespace) {
199
199
  let newContent = content;
@@ -205,14 +205,14 @@ export function registerListDirectoryTool(server, options = {}) {
205
205
  onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_DIRECTORY, args.path ?? '.'),
206
206
  });
207
207
  registerStandardTool(server, LIST_DIRECTORY_TOOL, handler, options, {
208
- progressMessage: (args) => `≣ ls: ${args.path ? basename(args.path) : '.'}`,
208
+ progressMessage: (args) => `${LIST_DIRECTORY_TOOL.title}: ${args.path ? basename(args.path) : '.'}`,
209
209
  completionMessage: (args, result) => {
210
210
  const base = args.path ? basename(args.path) : '.';
211
211
  if (result.isError)
212
- return `≣ ls: ${base} • failed`;
212
+ return `${LIST_DIRECTORY_TOOL.title}: ${base} • ${result.errorCode}`;
213
213
  const sc = result.structuredContent;
214
214
  const count = sc.totalEntries ?? 0;
215
- return `≣ ls: ${base} • ${count} ${count === 1 ? 'entry' : 'entries'}`;
215
+ return `${LIST_DIRECTORY_TOOL.title}: ${base} • ${count} ${count === 1 ? 'entry' : 'entries'}`;
216
216
  },
217
217
  });
218
218
  }
@@ -157,23 +157,22 @@ export function registerMoveFileTool(server, options = {}) {
157
157
  progressMessage: (args) => {
158
158
  const dest = basename(args.destination);
159
159
  if (args.source && !args.sources?.length) {
160
- return `🛠 mv: ${basename(args.source)} → ${dest}`;
160
+ return `${MOVE_FILE_TOOL.title}: ${basename(args.source)} → ${dest}`;
161
161
  }
162
162
  const count = (args.source ? 1 : 0) + (args.sources?.length ?? 0);
163
- return `🛠 mv: ${count} items → ${dest}`;
163
+ return `${MOVE_FILE_TOOL.title}: ${count} items → ${dest}`;
164
164
  },
165
165
  completionMessage: (args, result) => {
166
166
  const dest = basename(args.destination);
167
167
  if (args.source && !args.sources?.length) {
168
168
  const src = basename(args.source);
169
169
  if (result.isError)
170
- return `🛠 mv: ${src} → ${dest} • failed`;
171
- return `🛠 mv: ${src} → ${dest}`;
170
+ return `${MOVE_FILE_TOOL.title}: ${src} → ${dest} • ${result.errorCode}`;
172
171
  }
173
172
  const count = (args.source ? 1 : 0) + (args.sources?.length ?? 0);
174
173
  if (result.isError)
175
- return `🛠 mv: ${count} items → ${dest} • failed`;
176
- return `🛠 mv: ${count} items → ${dest}`;
174
+ return `${MOVE_FILE_TOOL.title}: ${count} items → ${dest} • ${result.errorCode}`;
175
+ return `${MOVE_FILE_TOOL.title}: ${count} items → ${dest}`;
177
176
  },
178
177
  });
179
178
  }