@j0hanz/filesystem-mcp 1.2.3 → 1.3.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.
- package/README.md +8 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +13 -1
- package/dist/completions.d.ts +1 -1
- package/dist/completions.js +36 -1
- package/dist/index.js +26 -8
- package/dist/lib/observability.d.ts +6 -0
- package/dist/lib/observability.js +1 -1
- package/dist/lib/resource-store.js +53 -0
- package/dist/prompts.js +34 -14
- package/dist/resources/generated-instructions.d.ts +1 -0
- package/dist/resources/generated-instructions.js +100 -0
- package/dist/resources.d.ts +1 -0
- package/dist/resources.js +36 -1
- package/dist/schemas.d.ts +6 -0
- package/dist/schemas.js +24 -0
- package/dist/server/bootstrap.d.ts +2 -0
- package/dist/server/bootstrap.js +226 -20
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/tools/apply-patch.d.ts +2 -1
- package/dist/tools/apply-patch.js +7 -5
- package/dist/tools/calculate-hash.d.ts +2 -1
- package/dist/tools/calculate-hash.js +9 -5
- package/dist/tools/contract.d.ts +41 -0
- package/dist/tools/contract.js +1 -0
- package/dist/tools/create-directory.d.ts +2 -1
- package/dist/tools/create-directory.js +6 -5
- package/dist/tools/delete-file.d.ts +2 -1
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/diff-files.d.ts +2 -1
- package/dist/tools/diff-files.js +7 -4
- package/dist/tools/edit-file.d.ts +2 -1
- package/dist/tools/edit-file.js +19 -6
- package/dist/tools/list-directory.d.ts +2 -1
- package/dist/tools/list-directory.js +36 -7
- package/dist/tools/move-file.d.ts +2 -1
- package/dist/tools/move-file.js +7 -5
- package/dist/tools/read-multiple.d.ts +2 -1
- package/dist/tools/read-multiple.js +10 -5
- package/dist/tools/read.d.ts +2 -1
- package/dist/tools/read.js +9 -5
- package/dist/tools/replace-in-files.d.ts +2 -1
- package/dist/tools/replace-in-files.js +14 -7
- package/dist/tools/roots.d.ts +2 -1
- package/dist/tools/roots.js +7 -4
- package/dist/tools/search-content.d.ts +2 -1
- package/dist/tools/search-content.js +14 -6
- package/dist/tools/search-files.d.ts +2 -1
- package/dist/tools/search-files.js +39 -7
- package/dist/tools/shared.d.ts +2 -2
- package/dist/tools/shared.js +16 -1
- package/dist/tools/stat-many.d.ts +2 -1
- package/dist/tools/stat-many.js +7 -5
- package/dist/tools/stat.d.ts +2 -1
- package/dist/tools/stat.js +7 -4
- package/dist/tools/task-support.d.ts +2 -0
- package/dist/tools/task-support.js +48 -7
- package/dist/tools/tree.d.ts +2 -1
- package/dist/tools/tree.js +7 -5
- package/dist/tools/write-file.d.ts +2 -1
- package/dist/tools/write-file.js +12 -5
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +39 -18
- package/package.json +1 -2
- package/dist/instructions.md +0 -200
|
@@ -5,7 +5,8 @@ import { ErrorCode } from '../lib/errors.js';
|
|
|
5
5
|
import { listDirectory } from '../lib/file-operations/list-directory.js';
|
|
6
6
|
import { ListDirectoryInputSchema, ListDirectoryOutputSchema, } from '../schemas.js';
|
|
7
7
|
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
|
-
const LIST_DIRECTORY_TOOL = {
|
|
8
|
+
export const LIST_DIRECTORY_TOOL = {
|
|
9
|
+
name: 'ls',
|
|
9
10
|
title: 'List Directory',
|
|
10
11
|
description: 'List the immediate contents of a directory (non-recursive). ' +
|
|
11
12
|
'Returns name, relative path, type (file/directory/symlink), size, and modified date. ' +
|
|
@@ -15,6 +16,7 @@ const LIST_DIRECTORY_TOOL = {
|
|
|
15
16
|
inputSchema: ListDirectoryInputSchema,
|
|
16
17
|
outputSchema: ListDirectoryOutputSchema,
|
|
17
18
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
19
|
+
nuances: ['`pattern` enables filtered recursive traversal up to `maxDepth`.'],
|
|
18
20
|
};
|
|
19
21
|
function buildListTextResult(result) {
|
|
20
22
|
const { entries, summary, path } = result;
|
|
@@ -53,7 +55,7 @@ function buildStructuredListEntry(entry) {
|
|
|
53
55
|
modified: entry.modified?.toISOString(),
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
|
-
function buildStructuredListResult(result) {
|
|
58
|
+
function buildStructuredListResult(result, nextCursor) {
|
|
57
59
|
const { entries, summary, path: resultPath } = result;
|
|
58
60
|
const structuredEntries = [];
|
|
59
61
|
for (const entry of entries) {
|
|
@@ -71,22 +73,48 @@ function buildStructuredListResult(result) {
|
|
|
71
73
|
...(summary.skippedInaccessible
|
|
72
74
|
? { skippedInaccessible: summary.skippedInaccessible }
|
|
73
75
|
: {}),
|
|
76
|
+
...(nextCursor !== undefined ? { nextCursor } : {}),
|
|
74
77
|
};
|
|
75
78
|
}
|
|
79
|
+
function encodeCursor(offset) {
|
|
80
|
+
return Buffer.from(JSON.stringify({ offset })).toString('base64url');
|
|
81
|
+
}
|
|
82
|
+
function decodeCursor(cursor) {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf-8'));
|
|
85
|
+
if (typeof parsed === 'object' &&
|
|
86
|
+
parsed !== null &&
|
|
87
|
+
typeof parsed.offset === 'number') {
|
|
88
|
+
const { offset } = parsed;
|
|
89
|
+
return Number.isInteger(offset) && offset >= 0 ? offset : 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// ignore malformed cursor
|
|
94
|
+
}
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
76
97
|
async function handleListDirectory(args, signal) {
|
|
77
98
|
const dirPath = resolvePathOrRoot(args.path);
|
|
99
|
+
const cursorOffset = args.cursor !== undefined ? decodeCursor(args.cursor) : 0;
|
|
100
|
+
const pageSize = args.maxEntries ?? 20_000;
|
|
78
101
|
const options = {
|
|
79
102
|
includeHidden: args.includeHidden,
|
|
80
103
|
excludePatterns: args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS,
|
|
81
104
|
sortBy: args.sortBy,
|
|
82
105
|
includeSymlinkTargets: args.includeSymlinkTargets,
|
|
83
106
|
...(args.maxDepth !== undefined ? { maxDepth: args.maxDepth } : {}),
|
|
84
|
-
|
|
107
|
+
maxEntries: cursorOffset + pageSize,
|
|
85
108
|
...(args.pattern !== undefined ? { pattern: args.pattern } : {}),
|
|
86
109
|
...(signal ? { signal } : {}),
|
|
87
110
|
};
|
|
88
111
|
const result = await listDirectory(dirPath, options);
|
|
89
|
-
|
|
112
|
+
const displayEntries = cursorOffset > 0 ? result.entries.slice(cursorOffset) : result.entries;
|
|
113
|
+
const nextCursor = result.summary.truncated && displayEntries.length > 0
|
|
114
|
+
? encodeCursor(cursorOffset + displayEntries.length)
|
|
115
|
+
: undefined;
|
|
116
|
+
const displayResult = { ...result, entries: displayEntries };
|
|
117
|
+
return buildToolResponse(buildListTextResult(displayResult), buildStructuredListResult(displayResult, nextCursor));
|
|
90
118
|
}
|
|
91
119
|
export function registerListDirectoryTool(server, options = {}) {
|
|
92
120
|
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
@@ -96,8 +124,7 @@ export function registerListDirectoryTool(server, options = {}) {
|
|
|
96
124
|
run: (signal) => handleListDirectory(args, signal),
|
|
97
125
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, args.path ?? '.'),
|
|
98
126
|
});
|
|
99
|
-
const
|
|
100
|
-
server.registerTool('ls', withDefaultIcons({ ...LIST_DIRECTORY_TOOL }, options.iconInfo), wrapToolHandler(validatedHandler, {
|
|
127
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
101
128
|
guard: options.isInitialized,
|
|
102
129
|
progressMessage: (args) => {
|
|
103
130
|
if (args.path) {
|
|
@@ -115,5 +142,7 @@ export function registerListDirectoryTool(server, options = {}) {
|
|
|
115
142
|
const count = sc.totalEntries ?? 0;
|
|
116
143
|
return `≣ ls: ${base} • ${count} ${count === 1 ? 'entry' : 'entries'}`;
|
|
117
144
|
},
|
|
118
|
-
})
|
|
145
|
+
});
|
|
146
|
+
const validatedHandler = withValidatedArgs(ListDirectoryInputSchema, wrappedHandler);
|
|
147
|
+
server.registerTool('ls', withDefaultIcons({ ...LIST_DIRECTORY_TOOL }, options.iconInfo), validatedHandler);
|
|
119
148
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const MOVE_FILE_TOOL: ToolContract;
|
|
3
4
|
export declare function registerMoveFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/move-file.js
CHANGED
|
@@ -6,12 +6,14 @@ import { validateExistingPath, validatePathForWrite, } from '../lib/path-validat
|
|
|
6
6
|
import { MoveFileInputSchema, MoveFileOutputSchema } from '../schemas.js';
|
|
7
7
|
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
|
-
const MOVE_FILE_TOOL = {
|
|
9
|
+
export const MOVE_FILE_TOOL = {
|
|
10
|
+
name: 'mv',
|
|
10
11
|
title: 'Move File',
|
|
11
12
|
description: 'Move or rename a file or directory.',
|
|
12
13
|
inputSchema: MoveFileInputSchema,
|
|
13
14
|
outputSchema: MoveFileOutputSchema,
|
|
14
15
|
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
16
|
+
nuances: ['Cross-device moves fall back to copy+delete.'],
|
|
15
17
|
};
|
|
16
18
|
async function handleMoveFile(args, signal) {
|
|
17
19
|
const validSource = await validateExistingPath(args.source, signal);
|
|
@@ -46,8 +48,7 @@ export function registerMoveFileTool(server, options = {}) {
|
|
|
46
48
|
run: (signal) => handleMoveFile(args, signal),
|
|
47
49
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.source),
|
|
48
50
|
});
|
|
49
|
-
const
|
|
50
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
51
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
51
52
|
guard: options.isInitialized,
|
|
52
53
|
progressMessage: (args) => `🛠 mv: ${path.basename(args.source)} → ${path.basename(args.destination)}`,
|
|
53
54
|
completionMessage: (args, result) => {
|
|
@@ -58,7 +59,8 @@ export function registerMoveFileTool(server, options = {}) {
|
|
|
58
59
|
return `🛠 mv: ${src} → ${dst} • moved`;
|
|
59
60
|
},
|
|
60
61
|
});
|
|
61
|
-
|
|
62
|
+
const validatedHandler = withValidatedArgs(MoveFileInputSchema, wrappedHandler);
|
|
63
|
+
if (registerToolTaskIfAvailable(server, 'mv', MOVE_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
62
64
|
return;
|
|
63
|
-
server.registerTool('mv', withDefaultIcons({ ...MOVE_FILE_TOOL }, options.iconInfo),
|
|
65
|
+
server.registerTool('mv', withDefaultIcons({ ...MOVE_FILE_TOOL }, options.iconInfo), validatedHandler);
|
|
64
66
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const READ_MULTIPLE_FILES_TOOL: ToolContract;
|
|
3
4
|
export declare function registerReadMultipleFilesTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
|
@@ -5,7 +5,8 @@ import { readMultipleFiles } from '../lib/file-operations/read-multiple-files.js
|
|
|
5
5
|
import { ReadMultipleFilesInputSchema, ReadMultipleFilesOutputSchema, } from '../schemas.js';
|
|
6
6
|
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
7
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
8
|
-
const READ_MULTIPLE_FILES_TOOL = {
|
|
8
|
+
export const READ_MULTIPLE_FILES_TOOL = {
|
|
9
|
+
name: 'read_many',
|
|
9
10
|
title: 'Read Multiple Files',
|
|
10
11
|
description: 'Read multiple text files in a single request. ' +
|
|
11
12
|
'Returns contents and metadata for each file. ' +
|
|
@@ -13,6 +14,10 @@ const READ_MULTIPLE_FILES_TOOL = {
|
|
|
13
14
|
inputSchema: ReadMultipleFilesInputSchema,
|
|
14
15
|
outputSchema: ReadMultipleFilesOutputSchema,
|
|
15
16
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
17
|
+
nuances: ['Total read budget is capped by `MAX_READ_MANY_TOTAL_SIZE`.'],
|
|
18
|
+
gotchas: [
|
|
19
|
+
'Per-file `truncationReason` can be `head`, `range`, or `externalized`.',
|
|
20
|
+
],
|
|
16
21
|
};
|
|
17
22
|
async function handleReadMultipleFiles(args, signal, resourceStore) {
|
|
18
23
|
const options = {
|
|
@@ -121,8 +126,7 @@ export function registerReadMultipleFilesTool(server, options = {}) {
|
|
|
121
126
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FILE, primaryPath),
|
|
122
127
|
});
|
|
123
128
|
};
|
|
124
|
-
const
|
|
125
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
129
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
126
130
|
guard: options.isInitialized,
|
|
127
131
|
progressMessage: (args) => {
|
|
128
132
|
const first = path.basename(args.paths[0] ?? '');
|
|
@@ -143,7 +147,8 @@ export function registerReadMultipleFilesTool(server, options = {}) {
|
|
|
143
147
|
return `🕮 read_many: ${total} files read`;
|
|
144
148
|
},
|
|
145
149
|
});
|
|
146
|
-
|
|
150
|
+
const validatedHandler = withValidatedArgs(ReadMultipleFilesInputSchema, wrappedHandler);
|
|
151
|
+
if (registerToolTaskIfAvailable(server, 'read_many', READ_MULTIPLE_FILES_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
147
152
|
return;
|
|
148
|
-
server.registerTool('read_many', withDefaultIcons({ ...READ_MULTIPLE_FILES_TOOL }, options.iconInfo),
|
|
153
|
+
server.registerTool('read_many', withDefaultIcons({ ...READ_MULTIPLE_FILES_TOOL }, options.iconInfo), validatedHandler);
|
|
149
154
|
}
|
package/dist/tools/read.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const READ_FILE_TOOL: ToolContract;
|
|
3
4
|
export declare function registerReadFileTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/read.js
CHANGED
|
@@ -5,7 +5,8 @@ import { readFile } from '../lib/fs-helpers.js';
|
|
|
5
5
|
import { ReadFileInputSchema, ReadFileOutputSchema } from '../schemas.js';
|
|
6
6
|
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
7
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
8
|
-
const READ_FILE_TOOL = {
|
|
8
|
+
export const READ_FILE_TOOL = {
|
|
9
|
+
name: 'read',
|
|
9
10
|
title: 'Read File',
|
|
10
11
|
description: 'Read the text contents of a file. ' +
|
|
11
12
|
'Use head parameter to preview the first N lines of large files. ' +
|
|
@@ -13,6 +14,9 @@ const READ_FILE_TOOL = {
|
|
|
13
14
|
inputSchema: ReadFileInputSchema,
|
|
14
15
|
outputSchema: ReadFileOutputSchema,
|
|
15
16
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
17
|
+
nuances: [
|
|
18
|
+
'Large content is externalized to `filesystem-mcp://result/{id}` and preview is returned inline.',
|
|
19
|
+
],
|
|
16
20
|
};
|
|
17
21
|
async function handleReadFile(args, signal, resourceStore) {
|
|
18
22
|
const options = {
|
|
@@ -80,8 +84,7 @@ export function registerReadFileTool(server, options = {}) {
|
|
|
80
84
|
run: (signal) => handleReadFile(args, signal, options.resourceStore),
|
|
81
85
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FILE, args.path),
|
|
82
86
|
});
|
|
83
|
-
const
|
|
84
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
87
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
85
88
|
guard: options.isInitialized,
|
|
86
89
|
progressMessage: (args) => {
|
|
87
90
|
const name = path.basename(args.path);
|
|
@@ -105,7 +108,8 @@ export function registerReadFileTool(server, options = {}) {
|
|
|
105
108
|
return `🕮 read: ${name} • ${sc.totalLines ?? '?'} lines`;
|
|
106
109
|
},
|
|
107
110
|
});
|
|
108
|
-
|
|
111
|
+
const validatedHandler = withValidatedArgs(ReadFileInputSchema, wrappedHandler);
|
|
112
|
+
if (registerToolTaskIfAvailable(server, 'read', READ_FILE_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
109
113
|
return;
|
|
110
|
-
server.registerTool('read', withDefaultIcons({ ...READ_FILE_TOOL }, options.iconInfo),
|
|
114
|
+
server.registerTool('read', withDefaultIcons({ ...READ_FILE_TOOL }, options.iconInfo), validatedHandler);
|
|
111
115
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const SEARCH_AND_REPLACE_TOOL: ToolContract;
|
|
3
4
|
export declare function registerSearchAndReplaceTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
|
@@ -10,7 +10,8 @@ import { validateExistingPath, validatePathForWrite, } from '../lib/path-validat
|
|
|
10
10
|
import { SearchAndReplaceInputSchema, SearchAndReplaceOutputSchema, } from '../schemas.js';
|
|
11
11
|
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, notifyProgress, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
12
12
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
13
|
-
const SEARCH_AND_REPLACE_TOOL = {
|
|
13
|
+
export const SEARCH_AND_REPLACE_TOOL = {
|
|
14
|
+
name: 'search_and_replace',
|
|
14
15
|
title: 'Search and Replace',
|
|
15
16
|
description: 'Search and replace text across multiple files matching a glob pattern. ' +
|
|
16
17
|
'Replaces ALL occurrences in each file (unlike `edit` which replaces only the first). ' +
|
|
@@ -20,6 +21,12 @@ const SEARCH_AND_REPLACE_TOOL = {
|
|
|
20
21
|
inputSchema: SearchAndReplaceInputSchema,
|
|
21
22
|
outputSchema: SearchAndReplaceOutputSchema,
|
|
22
23
|
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
24
|
+
gotchas: [
|
|
25
|
+
'Literal mode is default; `isRegex=true` enables RE2 + capture replacements (`$1`, `$2`).',
|
|
26
|
+
],
|
|
27
|
+
nuances: [
|
|
28
|
+
'Changed-file sample and failure sample are capped/truncated in output.',
|
|
29
|
+
],
|
|
23
30
|
};
|
|
24
31
|
const MAX_FAILURES = 20;
|
|
25
32
|
const REPLACE_CONCURRENCY = Math.min(PARALLEL_CONCURRENCY, 8);
|
|
@@ -189,8 +196,8 @@ async function handleSearchAndReplace(args, signal, onProgress = () => { }) {
|
|
|
189
196
|
const entries = globEntries({
|
|
190
197
|
cwd: root,
|
|
191
198
|
pattern: args.filePattern,
|
|
192
|
-
excludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
|
193
|
-
includeHidden: false,
|
|
199
|
+
excludePatterns: args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS,
|
|
200
|
+
includeHidden: args.includeHidden ?? false,
|
|
194
201
|
baseNameMatch: false,
|
|
195
202
|
caseSensitiveMatch: true, // Default to sensitive for file paths
|
|
196
203
|
followSymbolicLinks: false,
|
|
@@ -279,11 +286,11 @@ export function registerSearchAndReplaceTool(server, options = {}) {
|
|
|
279
286
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
280
287
|
});
|
|
281
288
|
const { isInitialized } = options;
|
|
282
|
-
const
|
|
283
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
289
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
284
290
|
guard: isInitialized,
|
|
285
291
|
});
|
|
286
|
-
|
|
292
|
+
const validatedHandler = withValidatedArgs(SearchAndReplaceInputSchema, wrappedHandler);
|
|
293
|
+
if (registerToolTaskIfAvailable(server, 'search_and_replace', SEARCH_AND_REPLACE_TOOL, validatedHandler, options.iconInfo, isInitialized))
|
|
287
294
|
return;
|
|
288
|
-
server.registerTool('search_and_replace', withDefaultIcons({ ...SEARCH_AND_REPLACE_TOOL }, options.iconInfo),
|
|
295
|
+
server.registerTool('search_and_replace', withDefaultIcons({ ...SEARCH_AND_REPLACE_TOOL }, options.iconInfo), validatedHandler);
|
|
289
296
|
}
|
package/dist/tools/roots.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const LIST_ALLOWED_DIRECTORIES_TOOL: ToolContract;
|
|
3
4
|
export declare function registerListAllowedDirectoriesTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/roots.js
CHANGED
|
@@ -3,7 +3,8 @@ import { ErrorCode } from '../lib/errors.js';
|
|
|
3
3
|
import { getAllowedDirectories } from '../lib/path-validation.js';
|
|
4
4
|
import { ListAllowedDirectoriesInputSchema, ListAllowedDirectoriesOutputSchema, } from '../schemas.js';
|
|
5
5
|
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
6
|
-
const LIST_ALLOWED_DIRECTORIES_TOOL = {
|
|
6
|
+
export const LIST_ALLOWED_DIRECTORIES_TOOL = {
|
|
7
|
+
name: 'roots',
|
|
7
8
|
title: 'Workspace Roots',
|
|
8
9
|
description: 'List the workspace roots this server can access. ' +
|
|
9
10
|
'Call this first to see available directories. ' +
|
|
@@ -11,6 +12,7 @@ const LIST_ALLOWED_DIRECTORIES_TOOL = {
|
|
|
11
12
|
inputSchema: ListAllowedDirectoriesInputSchema,
|
|
12
13
|
outputSchema: ListAllowedDirectoriesOutputSchema,
|
|
13
14
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
15
|
+
nuances: ['Other tools are constrained to these roots.'],
|
|
14
16
|
};
|
|
15
17
|
function buildTextRoots(dirs) {
|
|
16
18
|
if (dirs.length === 0) {
|
|
@@ -38,8 +40,7 @@ export function registerListAllowedDirectoriesTool(server, options = {}) {
|
|
|
38
40
|
run: () => handleListAllowedDirectories(),
|
|
39
41
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN),
|
|
40
42
|
});
|
|
41
|
-
const
|
|
42
|
-
server.registerTool('roots', withDefaultIcons({ ...LIST_ALLOWED_DIRECTORIES_TOOL }, options.iconInfo), wrapToolHandler(validatedHandler, {
|
|
43
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
43
44
|
guard: options.isInitialized,
|
|
44
45
|
progressMessage: () => '≣ roots',
|
|
45
46
|
completionMessage: (_args, result) => {
|
|
@@ -51,5 +52,7 @@ export function registerListAllowedDirectoriesTool(server, options = {}) {
|
|
|
51
52
|
const count = sc.rootsCount ?? 0;
|
|
52
53
|
return `≣ roots • ${count} ${count === 1 ? 'root' : 'roots'}`;
|
|
53
54
|
},
|
|
54
|
-
})
|
|
55
|
+
});
|
|
56
|
+
const validatedHandler = withValidatedArgs(ListAllowedDirectoriesInputSchema, wrappedHandler);
|
|
57
|
+
server.registerTool('roots', withDefaultIcons({ ...LIST_ALLOWED_DIRECTORIES_TOOL }, options.iconInfo), validatedHandler);
|
|
55
58
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const SEARCH_CONTENT_TOOL: ToolContract;
|
|
3
4
|
export declare function registerSearchContentTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
|
@@ -7,8 +7,9 @@ import { searchContent } from '../lib/file-operations/search-content.js';
|
|
|
7
7
|
import { SearchContentInputSchema, SearchContentOutputSchema, } from '../schemas.js';
|
|
8
8
|
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createProgressReporter, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
9
9
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
10
|
-
const MAX_INLINE_MATCHES = 50;
|
|
11
|
-
const SEARCH_CONTENT_TOOL = {
|
|
10
|
+
const MAX_INLINE_MATCHES = parseInt(process.env['FS_CONTEXT_MAX_INLINE_MATCHES'] ?? '', 10) || 50;
|
|
11
|
+
export const SEARCH_CONTENT_TOOL = {
|
|
12
|
+
name: 'grep',
|
|
12
13
|
title: 'Search Content',
|
|
13
14
|
description: 'Search for text within file contents (grep-like). ' +
|
|
14
15
|
'Returns matching lines. ' +
|
|
@@ -18,6 +19,13 @@ const SEARCH_CONTENT_TOOL = {
|
|
|
18
19
|
inputSchema: SearchContentInputSchema,
|
|
19
20
|
outputSchema: SearchContentOutputSchema,
|
|
20
21
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
22
|
+
nuances: [
|
|
23
|
+
'Inline match rows are capped (first 50); full structured results are externalized via `resourceUri`.',
|
|
24
|
+
'Skips binary and oversized files.',
|
|
25
|
+
],
|
|
26
|
+
gotchas: [
|
|
27
|
+
'Inline match rows are capped (first 50); full structured results are externalized via `resourceUri`.',
|
|
28
|
+
],
|
|
21
29
|
};
|
|
22
30
|
function assertValidRegexPattern(pattern) {
|
|
23
31
|
try {
|
|
@@ -261,11 +269,11 @@ export function registerSearchContentTool(server, options = {}) {
|
|
|
261
269
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path ?? '.'),
|
|
262
270
|
});
|
|
263
271
|
const { isInitialized } = options;
|
|
264
|
-
const
|
|
265
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
272
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
266
273
|
guard: isInitialized,
|
|
267
274
|
});
|
|
268
|
-
|
|
275
|
+
const validatedHandler = withValidatedArgs(SearchContentInputSchema, wrappedHandler);
|
|
276
|
+
if (registerToolTaskIfAvailable(server, 'grep', SEARCH_CONTENT_TOOL, validatedHandler, options.iconInfo, isInitialized))
|
|
269
277
|
return;
|
|
270
|
-
server.registerTool('grep', withDefaultIcons({ ...SEARCH_CONTENT_TOOL }, options.iconInfo),
|
|
278
|
+
server.registerTool('grep', withDefaultIcons({ ...SEARCH_CONTENT_TOOL }, options.iconInfo), validatedHandler);
|
|
271
279
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const SEARCH_FILES_TOOL: ToolContract;
|
|
3
4
|
export declare function registerSearchFilesTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
|
@@ -6,7 +6,26 @@ import { searchFiles } from '../lib/file-operations/search-files.js';
|
|
|
6
6
|
import { SearchFilesInputSchema, SearchFilesOutputSchema } from '../schemas.js';
|
|
7
7
|
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
|
-
|
|
9
|
+
function encodeCursor(offset) {
|
|
10
|
+
return Buffer.from(JSON.stringify({ offset })).toString('base64url');
|
|
11
|
+
}
|
|
12
|
+
function decodeCursor(cursor) {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf-8'));
|
|
15
|
+
if (typeof parsed === 'object' &&
|
|
16
|
+
parsed !== null &&
|
|
17
|
+
typeof parsed.offset === 'number') {
|
|
18
|
+
const { offset } = parsed;
|
|
19
|
+
return Number.isInteger(offset) && offset >= 0 ? offset : 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// ignore malformed cursor
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
export const SEARCH_FILES_TOOL = {
|
|
28
|
+
name: 'find',
|
|
10
29
|
title: 'Find Files',
|
|
11
30
|
description: 'Find files by glob pattern (e.g., **/*.ts). ' +
|
|
12
31
|
'Returns a list of matching files with metadata. ' +
|
|
@@ -15,12 +34,19 @@ const SEARCH_FILES_TOOL = {
|
|
|
15
34
|
inputSchema: SearchFilesInputSchema,
|
|
16
35
|
outputSchema: SearchFilesOutputSchema,
|
|
17
36
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
37
|
+
nuances: [
|
|
38
|
+
'Respects `.gitignore` unless `includeIgnored=true`.',
|
|
39
|
+
'Returns relative paths plus metadata; may truncate.',
|
|
40
|
+
],
|
|
18
41
|
};
|
|
19
42
|
async function handleSearchFiles(args, signal, onProgress) {
|
|
20
43
|
const basePath = resolvePathOrRoot(args.path);
|
|
21
44
|
const excludePatterns = args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS;
|
|
45
|
+
const cursorOffset = args.cursor !== undefined ? decodeCursor(args.cursor) : 0;
|
|
46
|
+
const pageSize = args.maxResults;
|
|
47
|
+
const fetchMax = cursorOffset + pageSize;
|
|
22
48
|
const searchOptions = {
|
|
23
|
-
maxResults:
|
|
49
|
+
maxResults: fetchMax,
|
|
24
50
|
includeHidden: args.includeHidden,
|
|
25
51
|
sortBy: args.sortBy,
|
|
26
52
|
respectGitignore: !args.includeIgnored,
|
|
@@ -29,8 +55,13 @@ async function handleSearchFiles(args, signal, onProgress) {
|
|
|
29
55
|
...(signal ? { signal } : {}),
|
|
30
56
|
};
|
|
31
57
|
const result = await searchFiles(basePath, args.pattern, excludePatterns, searchOptions);
|
|
58
|
+
const allResults = result.results;
|
|
59
|
+
const displayResults = cursorOffset > 0 ? allResults.slice(cursorOffset) : allResults;
|
|
60
|
+
const nextCursor = result.summary.truncated && displayResults.length > 0
|
|
61
|
+
? encodeCursor(cursorOffset + displayResults.length)
|
|
62
|
+
: undefined;
|
|
32
63
|
const relativeResults = [];
|
|
33
|
-
for (const entry of
|
|
64
|
+
for (const entry of displayResults) {
|
|
34
65
|
relativeResults.push({
|
|
35
66
|
path: path.relative(result.basePath, entry.path),
|
|
36
67
|
size: entry.size,
|
|
@@ -53,6 +84,7 @@ async function handleSearchFiles(args, signal, onProgress) {
|
|
|
53
84
|
...(result.summary.stoppedReason
|
|
54
85
|
? { stoppedReason: result.summary.stoppedReason }
|
|
55
86
|
: {}),
|
|
87
|
+
...(nextCursor !== undefined ? { nextCursor } : {}),
|
|
56
88
|
};
|
|
57
89
|
let truncatedReason;
|
|
58
90
|
if (result.summary.truncated) {
|
|
@@ -152,11 +184,11 @@ export function registerSearchFilesTool(server, options = {}) {
|
|
|
152
184
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
153
185
|
});
|
|
154
186
|
const { isInitialized } = options;
|
|
155
|
-
const
|
|
156
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
187
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
157
188
|
guard: isInitialized,
|
|
158
189
|
});
|
|
159
|
-
|
|
190
|
+
const validatedHandler = withValidatedArgs(SearchFilesInputSchema, wrappedHandler);
|
|
191
|
+
if (registerToolTaskIfAvailable(server, 'find', SEARCH_FILES_TOOL, validatedHandler, options.iconInfo, isInitialized))
|
|
160
192
|
return;
|
|
161
|
-
server.registerTool('find', withDefaultIcons({ ...SEARCH_FILES_TOOL }, options.iconInfo),
|
|
193
|
+
server.registerTool('find', withDefaultIcons({ ...SEARCH_FILES_TOOL }, options.iconInfo), validatedHandler);
|
|
162
194
|
}
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { FileInfo } from '../config.js';
|
|
|
4
4
|
import { ErrorCode } from '../lib/errors.js';
|
|
5
5
|
import type { ResourceStore } from '../lib/resource-store.js';
|
|
6
6
|
import type { ToolErrorResponseSchema } from '../schemas.js';
|
|
7
|
+
export { type ToolContract } from './contract.js';
|
|
7
8
|
export declare const READ_ONLY_TOOL_ANNOTATIONS: {
|
|
8
9
|
readonly readOnlyHint: true;
|
|
9
10
|
readonly idempotentHint: true;
|
|
@@ -48,7 +49,7 @@ interface ToolErrorResponse extends Record<string, unknown> {
|
|
|
48
49
|
}
|
|
49
50
|
export type ToolResult<T> = ToolResponse<T> | ToolErrorResponse;
|
|
50
51
|
export declare function parseToolArgs<Schema extends z.ZodType>(schema: Schema, args: unknown): z.infer<Schema>;
|
|
51
|
-
export declare function withValidatedArgs<Args, Result>(schema: z.ZodType<Args>, handler: (args: Args, extra: ToolExtra) => Promise<ToolResult<Result>>): (args:
|
|
52
|
+
export declare function withValidatedArgs<Args, Result>(schema: z.ZodType<Args>, handler: (args: Args, extra: ToolExtra) => Promise<ToolResult<Result>>): (args: unknown, extra: ToolExtra) => Promise<ToolResult<Result>>;
|
|
52
53
|
type ProgressToken = string | number;
|
|
53
54
|
export interface ToolExtra {
|
|
54
55
|
signal?: AbortSignal;
|
|
@@ -116,4 +117,3 @@ export declare function wrapToolHandler<Args, Result>(handler: (args: Args, extr
|
|
|
116
117
|
completionMessage?: (args: Args, result: ToolResult<Result>) => string | undefined;
|
|
117
118
|
}): (args: Args, extra?: ToolExtra) => Promise<ToolResult<Result>>;
|
|
118
119
|
export declare function resolvePathOrRoot(pathValue: string | undefined): string;
|
|
119
|
-
export {};
|
package/dist/tools/shared.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import { channel } from 'node:diagnostics_channel';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
import { createDetailedError, ErrorCode, formatDetailedError, getSuggestion, McpError, } from '../lib/errors.js';
|
|
3
4
|
import { createTimedAbortSignal } from '../lib/fs-helpers.js';
|
|
4
5
|
import { withToolDiagnostics } from '../lib/observability.js';
|
|
5
6
|
import { getAllowedDirectories } from '../lib/path-validation.js';
|
|
6
|
-
|
|
7
|
+
export {} from './contract.js';
|
|
8
|
+
const MAX_INLINE_CONTENT_CHARS = parseInt(process.env['FS_CONTEXT_MAX_INLINE_CHARS'] ?? '', 10) || 20_000;
|
|
7
9
|
const MAX_INLINE_PREVIEW_CHARS = 4_000;
|
|
8
10
|
const PROGRESS_RATE_LIMIT_MS = 50;
|
|
9
11
|
const TRUE_ENV_VALUES = new Set(['1', 'true', 'yes']);
|
|
12
|
+
const CONTEXT_DIAGNOSTICS_CHANNEL = channel('filesystem-mcp:context');
|
|
13
|
+
function publishContextDiagnostics(event) {
|
|
14
|
+
if (!CONTEXT_DIAGNOSTICS_CHANNEL.hasSubscribers)
|
|
15
|
+
return;
|
|
16
|
+
CONTEXT_DIAGNOSTICS_CHANNEL.publish(event);
|
|
17
|
+
}
|
|
10
18
|
export const READ_ONLY_TOOL_ANNOTATIONS = {
|
|
11
19
|
readOnlyHint: true,
|
|
12
20
|
idempotentHint: true,
|
|
@@ -61,6 +69,13 @@ export function maybeExternalizeTextContent(resourceStore, content, params) {
|
|
|
61
69
|
...(params.mimeType !== undefined ? { mimeType: params.mimeType } : {}),
|
|
62
70
|
text: content,
|
|
63
71
|
});
|
|
72
|
+
publishContextDiagnostics({
|
|
73
|
+
phase: 'externalize_text',
|
|
74
|
+
name: params.name,
|
|
75
|
+
...(params.mimeType !== undefined ? { mimeType: params.mimeType } : {}),
|
|
76
|
+
chars: content.length,
|
|
77
|
+
uri: entry.uri,
|
|
78
|
+
});
|
|
64
79
|
return {
|
|
65
80
|
entry,
|
|
66
81
|
preview: buildTextPreview(content),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const GET_MULTIPLE_FILE_INFO_TOOL: ToolContract;
|
|
3
4
|
export declare function registerGetMultipleFileInfoTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/stat-many.js
CHANGED
|
@@ -6,12 +6,14 @@ import { getMultipleFileInfo } from '../lib/file-operations/file-info.js';
|
|
|
6
6
|
import { GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, } from '../schemas.js';
|
|
7
7
|
import { buildFileInfoPayload, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
|
-
const GET_MULTIPLE_FILE_INFO_TOOL = {
|
|
9
|
+
export const GET_MULTIPLE_FILE_INFO_TOOL = {
|
|
10
|
+
name: 'stat_many',
|
|
10
11
|
title: 'Get Multiple File Info',
|
|
11
12
|
description: 'Get metadata for multiple files or directories in one request.',
|
|
12
13
|
inputSchema: GetMultipleFileInfoInputSchema,
|
|
13
14
|
outputSchema: GetMultipleFileInfoOutputSchema,
|
|
14
15
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
16
|
+
nuances: ['Use before read/search when file size/type uncertainty exists.'],
|
|
15
17
|
};
|
|
16
18
|
function formatFileInfoDetail(info) {
|
|
17
19
|
const lines = [
|
|
@@ -73,8 +75,7 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
|
73
75
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, primaryPath),
|
|
74
76
|
});
|
|
75
77
|
};
|
|
76
|
-
const
|
|
77
|
-
const wrappedHandler = wrapToolHandler(validatedHandler, {
|
|
78
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
78
79
|
guard: options.isInitialized,
|
|
79
80
|
progressMessage: (args) => {
|
|
80
81
|
const first = path.basename(args.paths[0] ?? '');
|
|
@@ -95,7 +96,8 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
|
95
96
|
return `🕮 stat_many: ${total} OK`;
|
|
96
97
|
},
|
|
97
98
|
});
|
|
98
|
-
|
|
99
|
+
const validatedHandler = withValidatedArgs(GetMultipleFileInfoInputSchema, wrappedHandler);
|
|
100
|
+
if (registerToolTaskIfAvailable(server, 'stat_many', GET_MULTIPLE_FILE_INFO_TOOL, validatedHandler, options.iconInfo, options.isInitialized))
|
|
99
101
|
return;
|
|
100
|
-
server.registerTool('stat_many', withDefaultIcons({ ...GET_MULTIPLE_FILE_INFO_TOOL }, options.iconInfo),
|
|
102
|
+
server.registerTool('stat_many', withDefaultIcons({ ...GET_MULTIPLE_FILE_INFO_TOOL }, options.iconInfo), validatedHandler);
|
|
101
103
|
}
|
package/dist/tools/stat.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { type ToolRegistrationOptions } from './shared.js';
|
|
2
|
+
import { type ToolContract, type ToolRegistrationOptions } from './shared.js';
|
|
3
|
+
export declare const GET_FILE_INFO_TOOL: ToolContract;
|
|
3
4
|
export declare function registerGetFileInfoTool(server: McpServer, options?: ToolRegistrationOptions): void;
|
package/dist/tools/stat.js
CHANGED
|
@@ -5,12 +5,14 @@ import { ErrorCode } from '../lib/errors.js';
|
|
|
5
5
|
import { getFileInfo } from '../lib/file-operations/file-info.js';
|
|
6
6
|
import { GetFileInfoInputSchema, GetFileInfoOutputSchema } from '../schemas.js';
|
|
7
7
|
import { buildFileInfoPayload, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
|
-
const GET_FILE_INFO_TOOL = {
|
|
8
|
+
export const GET_FILE_INFO_TOOL = {
|
|
9
|
+
name: 'stat',
|
|
9
10
|
title: 'Get File Info',
|
|
10
11
|
description: 'Get metadata (size, modified time, permissions, mime type) for a file or directory.',
|
|
11
12
|
inputSchema: GetFileInfoInputSchema,
|
|
12
13
|
outputSchema: GetFileInfoOutputSchema,
|
|
13
14
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
15
|
+
nuances: ['Use before read/search when file size/type uncertainty exists.'],
|
|
14
16
|
};
|
|
15
17
|
function formatFileInfoDetails(info) {
|
|
16
18
|
const lines = [
|
|
@@ -45,8 +47,7 @@ export function registerGetFileInfoTool(server, options = {}) {
|
|
|
45
47
|
run: (signal) => handleGetFileInfo(args, signal),
|
|
46
48
|
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, args.path),
|
|
47
49
|
});
|
|
48
|
-
const
|
|
49
|
-
server.registerTool('stat', withDefaultIcons({ ...GET_FILE_INFO_TOOL }, options.iconInfo), wrapToolHandler(validatedHandler, {
|
|
50
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
50
51
|
guard: options.isInitialized,
|
|
51
52
|
progressMessage: (args) => `🕮 stat: ${path.basename(args.path)}`,
|
|
52
53
|
completionMessage: (args, result) => {
|
|
@@ -58,5 +59,7 @@ export function registerGetFileInfoTool(server, options = {}) {
|
|
|
58
59
|
return `🕮 stat: ${name} • failed`;
|
|
59
60
|
return `🕮 stat: ${sc.info.name} • ${sc.info.type}, ${formatBytes(sc.info.size)}`;
|
|
60
61
|
},
|
|
61
|
-
})
|
|
62
|
+
});
|
|
63
|
+
const validatedHandler = withValidatedArgs(GetFileInfoInputSchema, wrappedHandler);
|
|
64
|
+
server.registerTool('stat', withDefaultIcons({ ...GET_FILE_INFO_TOOL }, options.iconInfo), validatedHandler);
|
|
62
65
|
}
|