@j0hanz/filesystem-mcp 1.7.2 → 1.7.3
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/lib/file-operations/common.d.ts +42 -0
- package/dist/lib/file-operations/common.js +87 -0
- package/dist/lib/file-operations/file-info.js +13 -19
- package/dist/lib/file-operations/glob-engine.d.ts +1 -6
- package/dist/lib/file-operations/glob-engine.js +0 -9
- package/dist/lib/file-operations/list-directory.js +19 -39
- package/dist/lib/file-operations/read-multiple-files.js +11 -19
- package/dist/lib/file-operations/search-content.js +106 -113
- package/dist/lib/file-operations/search-files.js +28 -72
- package/dist/lib/file-operations/tree.d.ts +2 -2
- package/dist/lib/file-operations/tree.js +20 -32
- package/dist/prompts.js +3 -3
- package/dist/resources/generated-instructions.js +14 -14
- package/dist/resources/tool-catalog.js +9 -9
- package/dist/resources/tool-info.js +5 -5
- package/dist/resources/workflows.js +17 -17
- package/dist/schemas.js +21 -90
- package/dist/server/bootstrap.js +2 -2
- package/dist/tools/list-directory.js +3 -21
- package/dist/tools/read-multiple.js +21 -19
- package/dist/tools/search-files.js +3 -21
- package/dist/tools/shared.d.ts +3 -0
- package/dist/tools/shared.js +27 -0
- package/dist/tools/stat-many.js +20 -20
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import { DEFAULT_EXCLUDE_PATTERNS } from '../lib/constants.js';
|
|
|
4
4
|
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
|
-
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, decodeOffsetCursor, encodeOffsetCursor, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
9
|
export const LIST_DIRECTORY_TOOL = {
|
|
10
10
|
name: 'ls',
|
|
@@ -82,27 +82,9 @@ function buildStructuredListResult(result, nextCursor) {
|
|
|
82
82
|
...(nextCursor !== undefined ? { nextCursor } : {}),
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
-
function encodeCursor(offset) {
|
|
86
|
-
return Buffer.from(JSON.stringify({ offset })).toString('base64url');
|
|
87
|
-
}
|
|
88
|
-
function decodeCursor(cursor) {
|
|
89
|
-
try {
|
|
90
|
-
const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf-8'));
|
|
91
|
-
if (typeof parsed === 'object' &&
|
|
92
|
-
parsed !== null &&
|
|
93
|
-
typeof parsed.offset === 'number') {
|
|
94
|
-
const { offset } = parsed;
|
|
95
|
-
return Number.isInteger(offset) && offset >= 0 ? offset : 0;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
// ignore malformed cursor
|
|
100
|
-
}
|
|
101
|
-
return 0;
|
|
102
|
-
}
|
|
103
85
|
async function handleListDirectory(args, signal) {
|
|
104
86
|
const dirPath = resolvePathOrRoot(args.path);
|
|
105
|
-
const cursorOffset = args.cursor !== undefined ?
|
|
87
|
+
const cursorOffset = args.cursor !== undefined ? decodeOffsetCursor(args.cursor) : 0;
|
|
106
88
|
const pageSize = args.maxEntries;
|
|
107
89
|
const options = {
|
|
108
90
|
includeHidden: args.includeHidden,
|
|
@@ -117,7 +99,7 @@ async function handleListDirectory(args, signal) {
|
|
|
117
99
|
const result = await listDirectory(dirPath, options);
|
|
118
100
|
const displayEntries = cursorOffset > 0 ? result.entries.slice(cursorOffset) : result.entries;
|
|
119
101
|
const nextCursor = result.summary.truncated && displayEntries.length > 0
|
|
120
|
-
?
|
|
102
|
+
? encodeOffsetCursor(cursorOffset + displayEntries.length)
|
|
121
103
|
: undefined;
|
|
122
104
|
const displayResult = { ...result, entries: displayEntries };
|
|
123
105
|
return buildToolResponse(buildListTextResult(displayResult, nextCursor), buildStructuredListResult(displayResult, nextCursor));
|
|
@@ -3,7 +3,7 @@ import { DEFAULT_READ_MANY_MAX_TOTAL_SIZE, DEFAULT_SEARCH_TIMEOUT_MS, } from '..
|
|
|
3
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
4
4
|
import { readMultipleFiles } from '../lib/file-operations/read-multiple-files.js';
|
|
5
5
|
import { ReadMultipleFilesInputSchema, ReadMultipleFilesOutputSchema, } from '../schemas.js';
|
|
6
|
-
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
6
|
+
import { buildBatchPathContext, buildResourceLink, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
7
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
8
8
|
export const READ_MULTIPLE_FILES_TOOL = {
|
|
9
9
|
name: 'read_many',
|
|
@@ -20,6 +20,23 @@ export const READ_MULTIPLE_FILES_TOOL = {
|
|
|
20
20
|
'Per-file `truncationReason` can be `head`, `range`, or `externalized`.',
|
|
21
21
|
],
|
|
22
22
|
};
|
|
23
|
+
function buildReadManyCompletionSuffix(summary) {
|
|
24
|
+
const total = summary?.total ?? 0;
|
|
25
|
+
const failed = summary?.failed ?? 0;
|
|
26
|
+
const succeeded = summary?.succeeded ?? 0;
|
|
27
|
+
if (failed) {
|
|
28
|
+
return `${succeeded}/${total} read, ${failed} failed`;
|
|
29
|
+
}
|
|
30
|
+
const label = total === 1 ? 'file' : 'files';
|
|
31
|
+
return `${total} ${label} read`;
|
|
32
|
+
}
|
|
33
|
+
function createReadManyProgressCallbacks(extra, context, totalPaths) {
|
|
34
|
+
const progress = createToolProgressSession(extra, `🕮 read_many: ${context}`);
|
|
35
|
+
const onReadComplete = () => {
|
|
36
|
+
progress.increment((current) => `🕮 read_many: ${context} [${current}/${totalPaths} read]`);
|
|
37
|
+
};
|
|
38
|
+
return { progress, onReadComplete };
|
|
39
|
+
}
|
|
23
40
|
function toStructuredReadManyResult(result) {
|
|
24
41
|
const structured = {
|
|
25
42
|
path: result.path,
|
|
@@ -134,28 +151,13 @@ export function registerReadMultipleFilesTool(server, options = {}) {
|
|
|
134
151
|
timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
|
|
135
152
|
context: { path: primaryPath },
|
|
136
153
|
run: async (signal) => {
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
? `, ${path.basename(args.paths[1] ?? '')}${args.paths.length > 2 ? '…' : ''}`
|
|
140
|
-
: '';
|
|
141
|
-
const context = `${args.paths.length} files [${first}${extraPaths}]`;
|
|
142
|
-
const progress = createToolProgressSession(extra, `🕮 read_many: ${context}`);
|
|
143
|
-
const onReadComplete = () => {
|
|
144
|
-
progress.increment((current) => `🕮 read_many: ${context} [${current}/${args.paths.length} read]`);
|
|
145
|
-
};
|
|
154
|
+
const context = buildBatchPathContext(args.paths, 'files');
|
|
155
|
+
const { progress, onReadComplete } = createReadManyProgressCallbacks(extra, context, args.paths.length);
|
|
146
156
|
try {
|
|
147
157
|
const result = await handleReadMultipleFiles(args, signal, options.resourceStore, onReadComplete);
|
|
148
158
|
const sc = result.structuredContent;
|
|
159
|
+
const suffix = buildReadManyCompletionSuffix(sc.summary);
|
|
149
160
|
const total = sc.summary?.total ?? 0;
|
|
150
|
-
const failed = sc.summary?.failed ?? 0;
|
|
151
|
-
const succeeded = sc.summary?.succeeded ?? 0;
|
|
152
|
-
let suffix;
|
|
153
|
-
if (failed) {
|
|
154
|
-
suffix = `${succeeded}/${total} read, ${failed} failed`;
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
suffix = `${total} files read`;
|
|
158
|
-
}
|
|
159
161
|
const finalCurrent = Math.max(total, progress.getCurrent() + 1);
|
|
160
162
|
progress.complete(`🕮 read_many: ${context} • ${suffix}`, finalCurrent);
|
|
161
163
|
return result;
|
|
@@ -4,26 +4,8 @@ import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_SEARCH_TIMEOUT_MS, } from '../lib/con
|
|
|
4
4
|
import { ErrorCode } from '../lib/errors.js';
|
|
5
5
|
import { searchFiles } from '../lib/file-operations/search-files.js';
|
|
6
6
|
import { SearchFilesInputSchema, SearchFilesOutputSchema } from '../schemas.js';
|
|
7
|
-
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, decodeOffsetCursor, encodeOffsetCursor, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
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
9
|
export const SEARCH_FILES_TOOL = {
|
|
28
10
|
name: 'find',
|
|
29
11
|
title: 'Find Files',
|
|
@@ -43,7 +25,7 @@ export const SEARCH_FILES_TOOL = {
|
|
|
43
25
|
async function handleSearchFiles(args, signal, onProgress) {
|
|
44
26
|
const basePath = resolvePathOrRoot(args.path);
|
|
45
27
|
const excludePatterns = args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS;
|
|
46
|
-
const cursorOffset = args.cursor !== undefined ?
|
|
28
|
+
const cursorOffset = args.cursor !== undefined ? decodeOffsetCursor(args.cursor) : 0;
|
|
47
29
|
const pageSize = args.maxResults;
|
|
48
30
|
const fetchMax = cursorOffset + pageSize;
|
|
49
31
|
const searchOptions = {
|
|
@@ -59,7 +41,7 @@ async function handleSearchFiles(args, signal, onProgress) {
|
|
|
59
41
|
const allResults = result.results;
|
|
60
42
|
const displayResults = cursorOffset > 0 ? allResults.slice(cursorOffset) : allResults;
|
|
61
43
|
const nextCursor = result.summary.truncated && displayResults.length > 0
|
|
62
|
-
?
|
|
44
|
+
? encodeOffsetCursor(cursorOffset + displayResults.length)
|
|
63
45
|
: undefined;
|
|
64
46
|
const relativeResults = [];
|
|
65
47
|
for (const entry of displayResults) {
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -139,3 +139,6 @@ export declare function wrapToolHandler<Args, Result>(handler: (args: Args, extr
|
|
|
139
139
|
* See `src/server/roots-manager.ts` for the update lifecycle.
|
|
140
140
|
*/
|
|
141
141
|
export declare function resolvePathOrRoot(pathValue: string | undefined): string;
|
|
142
|
+
export declare function encodeOffsetCursor(offset: number): string;
|
|
143
|
+
export declare function decodeOffsetCursor(cursor: string): number;
|
|
144
|
+
export declare function buildBatchPathContext(paths: readonly string[], unitLabel?: string): string;
|
package/dist/tools/shared.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
1
2
|
import { channel } from 'node:diagnostics_channel';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { parseTrueEnvFlag } from '../lib/constants.js';
|
|
@@ -403,3 +404,29 @@ export function resolvePathOrRoot(pathValue) {
|
|
|
403
404
|
}
|
|
404
405
|
return root;
|
|
405
406
|
}
|
|
407
|
+
export function encodeOffsetCursor(offset) {
|
|
408
|
+
return Buffer.from(JSON.stringify({ offset })).toString('base64url');
|
|
409
|
+
}
|
|
410
|
+
export function decodeOffsetCursor(cursor) {
|
|
411
|
+
try {
|
|
412
|
+
const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf-8'));
|
|
413
|
+
if (typeof parsed === 'object' &&
|
|
414
|
+
parsed !== null &&
|
|
415
|
+
typeof parsed.offset === 'number') {
|
|
416
|
+
const { offset } = parsed;
|
|
417
|
+
return Number.isInteger(offset) && offset >= 0 ? offset : 0;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
// ignore malformed cursor
|
|
422
|
+
}
|
|
423
|
+
return 0;
|
|
424
|
+
}
|
|
425
|
+
export function buildBatchPathContext(paths, unitLabel = 'paths') {
|
|
426
|
+
const normalizedLabel = paths.length === 1 ? unitLabel.replace(/s$/i, '') : unitLabel;
|
|
427
|
+
const first = path.basename(paths[0] ?? '');
|
|
428
|
+
const extraPaths = paths.length > 1
|
|
429
|
+
? `, ${path.basename(paths[1] ?? '')}${paths.length > 2 ? '…' : ''}`
|
|
430
|
+
: '';
|
|
431
|
+
return `${paths.length} ${normalizedLabel} [${first}${extraPaths}]`;
|
|
432
|
+
}
|
package/dist/tools/stat-many.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
1
|
import { formatBytes, joinLines } from '../config.js';
|
|
3
2
|
import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
|
|
4
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
5
4
|
import { getMultipleFileInfo } from '../lib/file-operations/file-info.js';
|
|
6
5
|
import { GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, } from '../schemas.js';
|
|
7
|
-
import { buildFileInfoPayload, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
6
|
+
import { buildBatchPathContext, buildFileInfoPayload, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
7
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
8
|
export const GET_MULTIPLE_FILE_INFO_TOOL = {
|
|
10
9
|
name: 'stat_many',
|
|
@@ -16,6 +15,22 @@ export const GET_MULTIPLE_FILE_INFO_TOOL = {
|
|
|
16
15
|
taskSupport: 'optional',
|
|
17
16
|
nuances: ['Use before read/search when file size/type uncertainty exists.'],
|
|
18
17
|
};
|
|
18
|
+
function buildStatManyCompletionSuffix(summary) {
|
|
19
|
+
const total = summary?.total ?? 0;
|
|
20
|
+
const failed = summary?.failed ?? 0;
|
|
21
|
+
const succeeded = summary?.succeeded ?? 0;
|
|
22
|
+
if (failed) {
|
|
23
|
+
return `${succeeded}/${total} OK, ${failed} failed`;
|
|
24
|
+
}
|
|
25
|
+
return `${total} OK`;
|
|
26
|
+
}
|
|
27
|
+
function createStatManyProgressCallbacks(extra, context, totalPaths) {
|
|
28
|
+
const progress = createToolProgressSession(extra, `🕮 stat_many: ${context}`);
|
|
29
|
+
const onProgress = () => {
|
|
30
|
+
progress.increment((current) => `🕮 stat_many: ${context} [${current}/${totalPaths} scanned]`);
|
|
31
|
+
};
|
|
32
|
+
return { progress, onProgress };
|
|
33
|
+
}
|
|
19
34
|
function formatFileInfoDetail(info) {
|
|
20
35
|
const lines = [
|
|
21
36
|
`${info.name} (${info.type})`,
|
|
@@ -74,28 +89,13 @@ export function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
|
74
89
|
timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
|
|
75
90
|
context: { path: primaryPath },
|
|
76
91
|
run: async (signal) => {
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
? `, ${path.basename(args.paths[1] ?? '')}${args.paths.length > 2 ? '…' : ''}`
|
|
80
|
-
: '';
|
|
81
|
-
const context = `${args.paths.length} paths [${first}${extraPaths}]`;
|
|
82
|
-
const progress = createToolProgressSession(extra, `🕮 stat_many: ${context}`);
|
|
83
|
-
const onProgress = () => {
|
|
84
|
-
progress.increment((current) => `🕮 stat_many: ${context} [${current}/${args.paths.length} scanned]`);
|
|
85
|
-
};
|
|
92
|
+
const context = buildBatchPathContext(args.paths);
|
|
93
|
+
const { progress, onProgress } = createStatManyProgressCallbacks(extra, context, args.paths.length);
|
|
86
94
|
try {
|
|
87
95
|
const result = await handleGetMultipleFileInfo(args, signal, onProgress);
|
|
88
96
|
const sc = result.structuredContent;
|
|
97
|
+
const suffix = buildStatManyCompletionSuffix(sc.summary);
|
|
89
98
|
const total = sc.summary?.total ?? 0;
|
|
90
|
-
const failed = sc.summary?.failed ?? 0;
|
|
91
|
-
const succeeded = sc.summary?.succeeded ?? 0;
|
|
92
|
-
let suffix;
|
|
93
|
-
if (failed) {
|
|
94
|
-
suffix = `${succeeded}/${total} OK, ${failed} failed`;
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
suffix = `${total} OK`;
|
|
98
|
-
}
|
|
99
99
|
const finalCurrent = Math.max(total, progress.getCurrent() + 1);
|
|
100
100
|
progress.complete(`🕮 stat_many: ${context} • ${suffix}`, finalCurrent);
|
|
101
101
|
return result;
|