@j0hanz/fs-context-mcp 2.4.2 → 2.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/resources.js CHANGED
@@ -14,9 +14,6 @@ export function registerInstructionResource(server, instructions, iconInfo) {
14
14
  {
15
15
  src: iconInfo.src,
16
16
  mimeType: iconInfo.mimeType,
17
- ...(iconInfo.mimeType === 'image/svg+xml'
18
- ? { sizes: ['any'] }
19
- : {}),
20
17
  },
21
18
  ],
22
19
  }
@@ -42,9 +39,6 @@ export function registerResultResources(server, store, iconInfo) {
42
39
  {
43
40
  src: iconInfo.src,
44
41
  mimeType: iconInfo.mimeType,
45
- ...(iconInfo.mimeType === 'image/svg+xml'
46
- ? { sizes: ['any'] }
47
- : {}),
48
42
  },
49
43
  ],
50
44
  }
package/dist/server.js CHANGED
@@ -260,26 +260,19 @@ catch (error) {
260
260
  console.error('[WARNING] Failed to load instructions.md:', error instanceof Error ? error.message : String(error));
261
261
  }
262
262
  async function getLocalIconInfo() {
263
- const candidates = [
264
- { name: 'logo.svg', mime: 'image/svg+xml' },
265
- { name: 'logo.png', mime: 'image/png' },
266
- { name: 'logo.jpg', mime: 'image/jpeg' },
267
- { name: 'logo.jpeg', mime: 'image/jpeg' },
268
- ];
269
- for (const { name, mime } of candidates) {
270
- try {
271
- const iconPath = new URL(`../assets/${name}`, import.meta.url);
272
- const buffer = await fs.readFile(iconPath);
273
- return {
274
- src: `data:${mime};base64,${buffer.toString('base64')}`,
275
- mimeType: mime,
276
- };
277
- }
278
- catch {
279
- continue;
280
- }
263
+ const name = 'logo.svg';
264
+ const mime = 'image/svg+xml';
265
+ try {
266
+ const iconPath = new URL(`../assets/${name}`, import.meta.url);
267
+ const buffer = await fs.readFile(iconPath);
268
+ return {
269
+ src: `data:${mime};base64,${buffer.toString('base64')}`,
270
+ mimeType: mime,
271
+ };
272
+ }
273
+ catch {
274
+ return undefined;
281
275
  }
282
- return undefined;
283
276
  }
284
277
  function resolveToolErrorCode(message) {
285
278
  return extractExplicitErrorCode(message);
@@ -331,9 +324,6 @@ export async function createServer(options = {}) {
331
324
  {
332
325
  src: localIcon.src,
333
326
  mimeType: localIcon.mimeType,
334
- ...(localIcon.mimeType === 'image/svg+xml'
335
- ? { sizes: ['any'] }
336
- : {}),
337
327
  },
338
328
  ],
339
329
  }
@@ -1,10 +1,11 @@
1
+ import * as path from 'node:path';
1
2
  import { formatOperationSummary, joinLines } from '../config.js';
2
3
  import { DEFAULT_EXCLUDE_PATTERNS } from '../lib/constants.js';
3
4
  import { ErrorCode } from '../lib/errors.js';
4
5
  import { listDirectory } from '../lib/file-operations/list-directory.js';
5
6
  import { withToolDiagnostics } from '../lib/observability.js';
6
7
  import { ListDirectoryInputSchema, ListDirectoryOutputSchema, } from '../schemas.js';
7
- import { buildToolErrorResponse, buildToolResponse, resolvePathOrRoot, withToolErrorHandling, wrapToolHandler, } from './shared.js';
8
+ import { buildToolErrorResponse, buildToolResponse, resolvePathOrRoot, withDefaultIcons, withToolErrorHandling, wrapToolHandler, } from './shared.js';
8
9
  const LIST_DIRECTORY_TOOL = {
9
10
  title: 'List Directory',
10
11
  description: 'List the immediate contents of a directory (non-recursive). ' +
@@ -80,23 +81,13 @@ async function handleListDirectory(args, signal) {
80
81
  }
81
82
  export function registerListDirectoryTool(server, options = {}) {
82
83
  const handler = (args, extra) => withToolDiagnostics('ls', () => withToolErrorHandling(() => handleListDirectory(args, extra.signal), (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, args.path ?? '.')), { path: args.path ?? '.' });
83
- server.registerTool('ls', {
84
- ...LIST_DIRECTORY_TOOL,
85
- ...(options.iconInfo
86
- ? {
87
- icons: [
88
- {
89
- src: options.iconInfo.src,
90
- mimeType: options.iconInfo.mimeType,
91
- ...(options.iconInfo.mimeType === 'image/svg+xml'
92
- ? { sizes: ['any'] }
93
- : {}),
94
- },
95
- ],
96
- }
97
- : {}),
98
- }, wrapToolHandler(handler, {
84
+ server.registerTool('ls', withDefaultIcons({ ...LIST_DIRECTORY_TOOL }, options.iconInfo), wrapToolHandler(handler, {
99
85
  guard: options.isInitialized,
100
- progressMessage: (args) => `ls \`${args.path ?? '.'}\``,
86
+ progressMessage: (args) => {
87
+ if (args.path) {
88
+ return `ls | ${path.basename(args.path)}`;
89
+ }
90
+ return 'ls';
91
+ },
101
92
  }));
102
93
  }
@@ -5,7 +5,7 @@ import { readMultipleFiles } from '../lib/file-operations/read-multiple-files.js
5
5
  import { createTimedAbortSignal } from '../lib/fs-helpers.js';
6
6
  import { withToolDiagnostics } from '../lib/observability.js';
7
7
  import { ReadMultipleFilesInputSchema, ReadMultipleFilesOutputSchema, } from '../schemas.js';
8
- import { buildResourceLink, buildToolErrorResponse, buildToolResponse, maybeExternalizeTextContent, withToolErrorHandling, wrapToolHandler, } from './shared.js';
8
+ import { buildResourceLink, buildToolErrorResponse, buildToolResponse, maybeExternalizeTextContent, withDefaultIcons, withToolErrorHandling, wrapToolHandler, } from './shared.js';
9
9
  const READ_MULTIPLE_FILES_TOOL = {
10
10
  title: 'Read Multiple Files',
11
11
  description: 'Read multiple text files in a single request. ' +
@@ -104,23 +104,8 @@ export function registerReadMultipleFilesTool(server, options = {}) {
104
104
  }
105
105
  }, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FILE, primaryPath)), { path: primaryPath });
106
106
  };
107
- server.registerTool('read_many', {
108
- ...READ_MULTIPLE_FILES_TOOL,
109
- ...(options.iconInfo
110
- ? {
111
- icons: [
112
- {
113
- src: options.iconInfo.src,
114
- mimeType: options.iconInfo.mimeType,
115
- ...(options.iconInfo.mimeType === 'image/svg+xml'
116
- ? { sizes: ['any'] }
117
- : {}),
118
- },
119
- ],
120
- }
121
- : {}),
122
- }, wrapToolHandler(handler, {
107
+ server.registerTool('read_many', withDefaultIcons({ ...READ_MULTIPLE_FILES_TOOL }, options.iconInfo), wrapToolHandler(handler, {
123
108
  guard: options.isInitialized,
124
- progressMessage: (args) => `read_many \`${args.paths.length}\` files`,
109
+ progressMessage: (args) => `read_many | ${args.paths.length} files`,
125
110
  }));
126
111
  }
@@ -5,7 +5,7 @@ import { readFile } from '../lib/fs-helpers.js';
5
5
  import { createTimedAbortSignal } from '../lib/fs-helpers.js';
6
6
  import { withToolDiagnostics } from '../lib/observability.js';
7
7
  import { ReadFileInputSchema, ReadFileOutputSchema } from '../schemas.js';
8
- import { buildResourceLink, buildToolErrorResponse, buildToolResponse, maybeExternalizeTextContent, withToolErrorHandling, wrapToolHandler, } from './shared.js';
8
+ import { buildResourceLink, buildToolErrorResponse, buildToolResponse, maybeExternalizeTextContent, withDefaultIcons, withToolErrorHandling, wrapToolHandler, } from './shared.js';
9
9
  const READ_FILE_TOOL = {
10
10
  title: 'Read File',
11
11
  description: 'Read the text contents of a file. ' +
@@ -87,23 +87,15 @@ export function registerReadFileTool(server, options = {}) {
87
87
  cleanup();
88
88
  }
89
89
  }, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FILE, args.path)), { path: args.path });
90
- server.registerTool('read', {
91
- ...READ_FILE_TOOL,
92
- ...(options.iconInfo
93
- ? {
94
- icons: [
95
- {
96
- src: options.iconInfo.src,
97
- mimeType: options.iconInfo.mimeType,
98
- ...(options.iconInfo.mimeType === 'image/svg+xml'
99
- ? { sizes: ['any'] }
100
- : {}),
101
- },
102
- ],
103
- }
104
- : {}),
105
- }, wrapToolHandler(handler, {
90
+ server.registerTool('read', withDefaultIcons({ ...READ_FILE_TOOL }, options.iconInfo), wrapToolHandler(handler, {
106
91
  guard: options.isInitialized,
107
- progressMessage: (args) => `read \`${args.path}\``,
92
+ progressMessage: (args) => {
93
+ const name = path.basename(args.path);
94
+ if (args.startLine !== undefined) {
95
+ const end = args.endLine ?? '…';
96
+ return `read | ${name} | ${args.startLine}-${end}`;
97
+ }
98
+ return `read | ${name}`;
99
+ },
108
100
  }));
109
101
  }
@@ -3,7 +3,7 @@ import { ErrorCode } from '../lib/errors.js';
3
3
  import { withToolDiagnostics } from '../lib/observability.js';
4
4
  import { getAllowedDirectories } from '../lib/path-validation.js';
5
5
  import { ListAllowedDirectoriesInputSchema, ListAllowedDirectoriesOutputSchema, } from '../schemas.js';
6
- import { buildToolErrorResponse, buildToolResponse, withToolErrorHandling, wrapToolHandler, } from './shared.js';
6
+ import { buildToolErrorResponse, buildToolResponse, withDefaultIcons, withToolErrorHandling, wrapToolHandler, } from './shared.js';
7
7
  const LIST_ALLOWED_DIRECTORIES_TOOL = {
8
8
  title: 'Workspace Roots',
9
9
  description: 'List the workspace roots this server can access. ' +
@@ -36,22 +36,7 @@ function handleListAllowedDirectories() {
36
36
  }
37
37
  export function registerListAllowedDirectoriesTool(server, options = {}) {
38
38
  const handler = () => withToolErrorHandling(() => withToolDiagnostics('roots', () => Promise.resolve(handleListAllowedDirectories())), (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN));
39
- server.registerTool('roots', {
40
- ...LIST_ALLOWED_DIRECTORIES_TOOL,
41
- ...(options.iconInfo
42
- ? {
43
- icons: [
44
- {
45
- src: options.iconInfo.src,
46
- mimeType: options.iconInfo.mimeType,
47
- ...(options.iconInfo.mimeType === 'image/svg+xml'
48
- ? { sizes: ['any'] }
49
- : {}),
50
- },
51
- ],
52
- }
53
- : {}),
54
- }, wrapToolHandler(handler, {
39
+ server.registerTool('roots', withDefaultIcons({ ...LIST_ALLOWED_DIRECTORIES_TOOL }, options.iconInfo), wrapToolHandler(handler, {
55
40
  guard: options.isInitialized,
56
41
  progressMessage: () => 'roots',
57
42
  }));
@@ -4,7 +4,7 @@ import { ErrorCode } from '../lib/errors.js';
4
4
  import { searchContent } from '../lib/file-operations/search-content.js';
5
5
  import { withToolDiagnostics } from '../lib/observability.js';
6
6
  import { SearchContentInputSchema, SearchContentOutputSchema, } from '../schemas.js';
7
- import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createProgressReporter, resolvePathOrRoot, withToolErrorHandling, wrapToolHandler, } from './shared.js';
7
+ import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createProgressReporter, resolvePathOrRoot, withDefaultIcons, withToolErrorHandling, wrapToolHandler, } from './shared.js';
8
8
  const MAX_INLINE_MATCHES = 50;
9
9
  const SEARCH_CONTENT_TOOL = {
10
10
  title: 'Search Content',
@@ -134,23 +134,15 @@ async function handleSearchContent(args, signal, resourceStore, onProgress) {
134
134
  }
135
135
  export function registerSearchContentTool(server, options = {}) {
136
136
  const handler = (args, extra) => withToolDiagnostics('grep', () => withToolErrorHandling(async () => handleSearchContent(args, extra.signal, options.resourceStore, createProgressReporter(extra)), (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path ?? '.')), { path: args.path ?? '.' });
137
- server.registerTool('grep', {
138
- ...SEARCH_CONTENT_TOOL,
139
- ...(options.iconInfo
140
- ? {
141
- icons: [
142
- {
143
- src: options.iconInfo.src,
144
- mimeType: options.iconInfo.mimeType,
145
- ...(options.iconInfo.mimeType === 'image/svg+xml'
146
- ? { sizes: ['any'] }
147
- : {}),
148
- },
149
- ],
150
- }
151
- : {}),
152
- }, wrapToolHandler(handler, {
137
+ server.registerTool('grep', withDefaultIcons({ ...SEARCH_CONTENT_TOOL }, options.iconInfo), wrapToolHandler(handler, {
153
138
  guard: options.isInitialized,
154
- progressMessage: (args) => `grep \`${args.pattern}\``,
139
+ progressMessage: (args) => `grep | ${args.pattern}`,
140
+ completionMessage: (args, result) => {
141
+ if ('isError' in result && result.isError)
142
+ return undefined;
143
+ const { structuredContent: sc } = result;
144
+ const suffix = sc.ok && sc.totalMatches ? String(sc.totalMatches) : 'No matches';
145
+ return `grep | ${args.pattern} | ${suffix}`;
146
+ },
155
147
  }));
156
148
  }
@@ -6,7 +6,7 @@ import { searchFiles } from '../lib/file-operations/search-files.js';
6
6
  import { createTimedAbortSignal } from '../lib/fs-helpers.js';
7
7
  import { withToolDiagnostics } from '../lib/observability.js';
8
8
  import { SearchFilesInputSchema, SearchFilesOutputSchema } from '../schemas.js';
9
- import { buildToolErrorResponse, buildToolResponse, resolvePathOrRoot, withToolErrorHandling, wrapToolHandler, } from './shared.js';
9
+ import { buildToolErrorResponse, buildToolResponse, resolvePathOrRoot, withDefaultIcons, withToolErrorHandling, wrapToolHandler, } from './shared.js';
10
10
  const SEARCH_FILES_TOOL = {
11
11
  title: 'Find Files',
12
12
  description: 'Find files by glob pattern (e.g., **/*.ts). ' +
@@ -74,23 +74,8 @@ export function registerSearchFilesTool(server, options = {}) {
74
74
  cleanup();
75
75
  }
76
76
  }, (error) => buildToolErrorResponse(error, ErrorCode.E_INVALID_PATTERN, args.path)), { path: args.path ?? '.' });
77
- server.registerTool('find', {
78
- ...SEARCH_FILES_TOOL,
79
- ...(options.iconInfo
80
- ? {
81
- icons: [
82
- {
83
- src: options.iconInfo.src,
84
- mimeType: options.iconInfo.mimeType,
85
- ...(options.iconInfo.mimeType === 'image/svg+xml'
86
- ? { sizes: ['any'] }
87
- : {}),
88
- },
89
- ],
90
- }
91
- : {}),
92
- }, wrapToolHandler(handler, {
77
+ server.registerTool('find', withDefaultIcons({ ...SEARCH_FILES_TOOL }, options.iconInfo), wrapToolHandler(handler, {
93
78
  guard: options.isInitialized,
94
- progressMessage: (args) => `find \`${args.pattern}\``,
79
+ progressMessage: (args) => `find | ${args.pattern}`,
95
80
  }));
96
81
  }
@@ -1,4 +1,4 @@
1
- import type { ContentBlock, ProgressNotificationParams } from '@modelcontextprotocol/sdk/types.js';
1
+ import type { ContentBlock, Icon, ProgressNotificationParams } from '@modelcontextprotocol/sdk/types.js';
2
2
  import { ErrorCode } from '../lib/errors.js';
3
3
  import type { ResourceStore } from '../lib/resource-store.js';
4
4
  type ResourceEntry = ReturnType<ResourceStore['putText']>;
@@ -56,6 +56,9 @@ export interface IconInfo {
56
56
  src: string;
57
57
  mimeType: string;
58
58
  }
59
+ export declare function withDefaultIcons<T extends object>(tool: T, iconInfo: IconInfo | undefined): T & {
60
+ icons?: Icon[];
61
+ };
59
62
  export interface ToolRegistrationOptions {
60
63
  resourceStore?: ResourceStore;
61
64
  isInitialized?: () => boolean;
@@ -71,6 +74,7 @@ export declare function createProgressReporter(extra: ToolExtra): (progress: {
71
74
  export declare function wrapToolHandler<Args, Result>(handler: (args: Args, extra: ToolExtra) => Promise<ToolResult<Result>>, options: {
72
75
  guard?: (() => boolean) | undefined;
73
76
  progressMessage?: (args: Args) => string;
77
+ completionMessage?: (args: Args, result: ToolResult<Result>) => string | undefined;
74
78
  }): (args: Args, extra?: ToolExtra) => Promise<ToolResult<Result>>;
75
79
  export declare function resolvePathOrRoot(pathValue: string | undefined): string;
76
80
  export {};
@@ -57,6 +57,23 @@ export function canSendProgress(extra) {
57
57
  return (extra._meta?.progressToken !== undefined &&
58
58
  extra.sendNotification !== undefined);
59
59
  }
60
+ export function withDefaultIcons(tool, iconInfo) {
61
+ if (!iconInfo)
62
+ return tool;
63
+ const existingIcons = tool.icons;
64
+ if (existingIcons && existingIcons.length > 0) {
65
+ return tool;
66
+ }
67
+ return {
68
+ ...tool,
69
+ icons: [
70
+ {
71
+ src: iconInfo.src,
72
+ mimeType: iconInfo.mimeType,
73
+ },
74
+ ],
75
+ };
76
+ }
60
77
  const NOT_INITIALIZED_ERROR = new McpError(ErrorCode.E_INVALID_INPUT, 'Client not initialized; wait for notifications/initialized');
61
78
  export async function withToolErrorHandling(run, onError) {
62
79
  try {
@@ -118,7 +135,7 @@ export function createProgressReporter(extra) {
118
135
  });
119
136
  };
120
137
  }
121
- async function withProgress(message, extra, run) {
138
+ async function withProgress(message, extra, run, getCompletionMessage) {
122
139
  if (!canSendProgress(extra)) {
123
140
  return await run();
124
141
  }
@@ -132,11 +149,12 @@ async function withProgress(message, extra, run) {
132
149
  });
133
150
  try {
134
151
  const result = await run();
152
+ const endMessage = getCompletionMessage?.(result) ?? message;
135
153
  await sendProgressNotification(extra, {
136
154
  progressToken: token,
137
155
  progress: total,
138
156
  total,
139
- message,
157
+ message: endMessage,
140
158
  });
141
159
  return result;
142
160
  }
@@ -158,7 +176,11 @@ export function wrapToolHandler(handler, options) {
158
176
  }
159
177
  if (options.progressMessage) {
160
178
  const message = options.progressMessage(args);
161
- return await withProgress(message, resolvedExtra, () => handler(args, resolvedExtra));
179
+ const { completionMessage } = options;
180
+ const completionFn = completionMessage
181
+ ? (result) => completionMessage(args, result)
182
+ : undefined;
183
+ return await withProgress(message, resolvedExtra, () => handler(args, resolvedExtra), completionFn);
162
184
  }
163
185
  return await handler(args, resolvedExtra);
164
186
  };
@@ -5,7 +5,7 @@ import { getMultipleFileInfo } from '../lib/file-operations/file-info.js';
5
5
  import { createTimedAbortSignal } from '../lib/fs-helpers.js';
6
6
  import { withToolDiagnostics } from '../lib/observability.js';
7
7
  import { GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, } from '../schemas.js';
8
- import { buildToolErrorResponse, buildToolResponse, withToolErrorHandling, wrapToolHandler, } from './shared.js';
8
+ import { buildToolErrorResponse, buildToolResponse, withDefaultIcons, withToolErrorHandling, wrapToolHandler, } from './shared.js';
9
9
  const GET_MULTIPLE_FILE_INFO_TOOL = {
10
10
  title: 'Get Multiple File Info',
11
11
  description: 'Get metadata for multiple files or directories in one request.',
@@ -84,23 +84,8 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
84
84
  }
85
85
  }, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, primaryPath)), { path: primaryPath });
86
86
  };
87
- server.registerTool('stat_many', {
88
- ...GET_MULTIPLE_FILE_INFO_TOOL,
89
- ...(options.iconInfo
90
- ? {
91
- icons: [
92
- {
93
- src: options.iconInfo.src,
94
- mimeType: options.iconInfo.mimeType,
95
- ...(options.iconInfo.mimeType === 'image/svg+xml'
96
- ? { sizes: ['any'] }
97
- : {}),
98
- },
99
- ],
100
- }
101
- : {}),
102
- }, wrapToolHandler(handler, {
87
+ server.registerTool('stat_many', withDefaultIcons({ ...GET_MULTIPLE_FILE_INFO_TOOL }, options.iconInfo), wrapToolHandler(handler, {
103
88
  guard: options.isInitialized,
104
- progressMessage: (args) => `stat_many \`${args.paths.length}\` paths`,
89
+ progressMessage: (args) => `stat_many ${args.paths.length} paths`,
105
90
  }));
106
91
  }
@@ -1,3 +1,4 @@
1
+ import * as path from 'node:path';
1
2
  import { formatBytes, joinLines } from '../config.js';
2
3
  import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
3
4
  import { ErrorCode } from '../lib/errors.js';
@@ -5,7 +6,7 @@ import { getFileInfo } from '../lib/file-operations/file-info.js';
5
6
  import { createTimedAbortSignal } from '../lib/fs-helpers.js';
6
7
  import { withToolDiagnostics } from '../lib/observability.js';
7
8
  import { GetFileInfoInputSchema, GetFileInfoOutputSchema } from '../schemas.js';
8
- import { buildToolErrorResponse, buildToolResponse, withToolErrorHandling, wrapToolHandler, } from './shared.js';
9
+ import { buildToolErrorResponse, buildToolResponse, withDefaultIcons, withToolErrorHandling, wrapToolHandler, } from './shared.js';
9
10
  const GET_FILE_INFO_TOOL = {
10
11
  title: 'Get File Info',
11
12
  description: 'Get metadata (size, modified time, permissions, mime type) for a file or directory.',
@@ -71,23 +72,8 @@ export function registerGetFileInfoTool(server, options = {}) {
71
72
  cleanup();
72
73
  }
73
74
  }, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, args.path)), { path: args.path });
74
- server.registerTool('stat', {
75
- ...GET_FILE_INFO_TOOL,
76
- ...(options.iconInfo
77
- ? {
78
- icons: [
79
- {
80
- src: options.iconInfo.src,
81
- mimeType: options.iconInfo.mimeType,
82
- ...(options.iconInfo.mimeType === 'image/svg+xml'
83
- ? { sizes: ['any'] }
84
- : {}),
85
- },
86
- ],
87
- }
88
- : {}),
89
- }, wrapToolHandler(handler, {
75
+ server.registerTool('stat', withDefaultIcons({ ...GET_FILE_INFO_TOOL }, options.iconInfo), wrapToolHandler(handler, {
90
76
  guard: options.isInitialized,
91
- progressMessage: (args) => `stat \`${args.path}\``,
77
+ progressMessage: (args) => `stat | ${path.basename(args.path)}`,
92
78
  }));
93
79
  }
@@ -1,10 +1,11 @@
1
+ import * as path from 'node:path';
1
2
  import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
2
3
  import { ErrorCode } from '../lib/errors.js';
3
4
  import { formatTreeAscii, treeDirectory } from '../lib/file-operations/tree.js';
4
5
  import { createTimedAbortSignal } from '../lib/fs-helpers.js';
5
6
  import { withToolDiagnostics } from '../lib/observability.js';
6
7
  import { TreeInputSchema, TreeOutputSchema } from '../schemas.js';
7
- import { buildToolErrorResponse, buildToolResponse, resolvePathOrRoot, withToolErrorHandling, wrapToolHandler, } from './shared.js';
8
+ import { buildToolErrorResponse, buildToolResponse, resolvePathOrRoot, withDefaultIcons, withToolErrorHandling, wrapToolHandler, } from './shared.js';
8
9
  const TREE_TOOL = {
9
10
  title: 'Tree',
10
11
  description: 'Render a directory tree (bounded recursion). ' +
@@ -51,23 +52,13 @@ export function registerTreeTool(server, options = {}) {
51
52
  }
52
53
  }, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, targetPath)), { path: targetPath });
53
54
  };
54
- server.registerTool('tree', {
55
- ...TREE_TOOL,
56
- ...(options.iconInfo
57
- ? {
58
- icons: [
59
- {
60
- src: options.iconInfo.src,
61
- mimeType: options.iconInfo.mimeType,
62
- ...(options.iconInfo.mimeType === 'image/svg+xml'
63
- ? { sizes: ['any'] }
64
- : {}),
65
- },
66
- ],
67
- }
68
- : {}),
69
- }, wrapToolHandler(handler, {
55
+ server.registerTool('tree', withDefaultIcons({ ...TREE_TOOL }, options.iconInfo), wrapToolHandler(handler, {
70
56
  guard: options.isInitialized,
71
- progressMessage: (args) => `tree \`${args.path ?? '.'}\``,
57
+ progressMessage: (args) => {
58
+ if (args.path) {
59
+ return `tree | ${path.basename(args.path)}`;
60
+ }
61
+ return 'tree';
62
+ },
72
63
  }));
73
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/fs-context-mcp",
3
- "version": "2.4.2",
3
+ "version": "2.4.4",
4
4
  "mcpName": "io.github.j0hanz/fs-context",
5
5
  "description": "🔍 Read-only MCP server for secure filesystem exploration, searching, and analysis",
6
6
  "type": "module",