@j0hanz/filesystem-mcp 1.1.2 → 1.2.1
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 +514 -188
- package/dist/cli.js +29 -12
- package/dist/completions.js +50 -24
- package/dist/config.d.ts +4 -2
- package/dist/config.js +2 -1
- package/dist/index.js +14 -12
- package/dist/instructions.md +109 -97
- package/dist/lib/constants.js +25 -14
- package/dist/lib/errors.js +15 -8
- package/dist/lib/file-operations/common.d.ts +4 -0
- package/dist/lib/file-operations/common.js +9 -0
- package/dist/lib/file-operations/file-info.js +22 -10
- package/dist/lib/file-operations/gitignore.js +14 -11
- package/dist/lib/file-operations/glob-engine.d.ts +1 -0
- package/dist/lib/file-operations/glob-engine.js +46 -33
- package/dist/lib/file-operations/list-directory.js +31 -35
- package/dist/lib/file-operations/read-multiple-files.js +70 -62
- package/dist/lib/file-operations/search-content.js +83 -64
- package/dist/lib/file-operations/search-files.js +32 -30
- package/dist/lib/file-operations/search-worker.js +22 -12
- package/dist/lib/file-operations/tree.js +43 -34
- package/dist/lib/fs-helpers.js +61 -124
- package/dist/lib/observability.js +29 -28
- package/dist/lib/path-format.d.ts +1 -0
- package/dist/lib/path-format.js +7 -0
- package/dist/lib/path-policy.js +22 -20
- package/dist/lib/path-validation.js +13 -7
- package/dist/lib/resource-store.d.ts +2 -0
- package/dist/lib/resource-store.js +26 -5
- package/dist/lib/type-guards.d.ts +1 -0
- package/dist/lib/type-guards.js +3 -0
- package/dist/prompts.d.ts +1 -5
- package/dist/prompts.js +9 -16
- package/dist/resources.d.ts +1 -5
- package/dist/resources.js +12 -26
- package/dist/schemas.d.ts +232 -30
- package/dist/schemas.js +52 -90
- package/dist/server.js +96 -44
- package/dist/tools/apply-patch.js +23 -22
- package/dist/tools/calculate-hash.js +41 -43
- package/dist/tools/create-directory.js +17 -19
- package/dist/tools/delete-file.js +35 -37
- package/dist/tools/diff-files.js +15 -19
- package/dist/tools/edit-file.js +15 -18
- package/dist/tools/list-directory.js +24 -23
- package/dist/tools/move-file.js +17 -19
- package/dist/tools/read-multiple.js +55 -66
- package/dist/tools/read.js +26 -30
- package/dist/tools/replace-in-files.js +27 -33
- package/dist/tools/roots.js +8 -8
- package/dist/tools/search-content.js +73 -72
- package/dist/tools/search-files.js +44 -50
- package/dist/tools/shared.d.ts +44 -6
- package/dist/tools/shared.js +86 -64
- package/dist/tools/stat-many.js +44 -66
- package/dist/tools/stat.js +10 -37
- package/dist/tools/task-support.d.ts +9 -1
- package/dist/tools/task-support.js +86 -81
- package/dist/tools/tree.js +12 -28
- package/dist/tools/write-file.js +17 -19
- package/dist/tools.js +23 -18
- package/package.json +6 -7
|
@@ -2,26 +2,22 @@ import * as fs from 'node:fs/promises';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { createReadStream } from 'node:fs';
|
|
5
|
+
import { PARALLEL_CONCURRENCY } from '../lib/constants.js';
|
|
5
6
|
import { ErrorCode } from '../lib/errors.js';
|
|
6
7
|
import { isIgnoredByGitignore, loadRootGitignore, } from '../lib/file-operations/gitignore.js';
|
|
7
8
|
import { globEntries } from '../lib/file-operations/glob-engine.js';
|
|
8
|
-
import { assertNotAborted,
|
|
9
|
-
import { withToolDiagnostics } from '../lib/observability.js';
|
|
9
|
+
import { assertNotAborted, withAbort } from '../lib/fs-helpers.js';
|
|
10
10
|
import { validateExistingPath } from '../lib/path-validation.js';
|
|
11
11
|
import { CalculateHashInputSchema, CalculateHashOutputSchema, } from '../schemas.js';
|
|
12
|
-
import { buildToolErrorResponse, buildToolResponse, createProgressReporter,
|
|
13
|
-
import {
|
|
12
|
+
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
13
|
+
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
14
14
|
const WINDOWS_PATH_SEPARATOR = /\\/gu;
|
|
15
15
|
const CALCULATE_HASH_TOOL = {
|
|
16
16
|
title: 'Calculate Hash',
|
|
17
17
|
description: 'Calculate SHA-256 hash of a file or directory.',
|
|
18
18
|
inputSchema: CalculateHashInputSchema,
|
|
19
19
|
outputSchema: CalculateHashOutputSchema,
|
|
20
|
-
annotations:
|
|
21
|
-
readOnlyHint: true,
|
|
22
|
-
idempotentHint: true,
|
|
23
|
-
openWorldHint: false,
|
|
24
|
-
},
|
|
20
|
+
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
25
21
|
};
|
|
26
22
|
async function hashFile(filePath, encoding, signal) {
|
|
27
23
|
assertNotAborted(signal);
|
|
@@ -64,9 +60,8 @@ function reportHashProgress(onProgress, current, force = false) {
|
|
|
64
60
|
async function hashDirectory(dirPath, options = {}) {
|
|
65
61
|
const { signal, onProgress } = options;
|
|
66
62
|
const gitignoreMatcher = await loadRootGitignore(dirPath, signal);
|
|
67
|
-
//
|
|
68
|
-
const
|
|
69
|
-
let filesHashed = 0;
|
|
63
|
+
// Phase 1: collect all file paths that pass gitignore filtering.
|
|
64
|
+
const filteredPaths = [];
|
|
70
65
|
for await (const entry of globEntries({
|
|
71
66
|
cwd: dirPath,
|
|
72
67
|
pattern: '**/*',
|
|
@@ -84,12 +79,25 @@ async function hashDirectory(dirPath, options = {}) {
|
|
|
84
79
|
isIgnoredByGitignore(gitignoreMatcher, dirPath, entry.path)) {
|
|
85
80
|
continue;
|
|
86
81
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
filteredPaths.push({
|
|
83
|
+
filePath: entry.path,
|
|
84
|
+
relativePath: toStableRelativePath(dirPath, entry.path),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
assertNotAborted(signal);
|
|
88
|
+
// Phase 2: hash files concurrently with bounded pool.
|
|
89
|
+
const concurrency = Math.min(PARALLEL_CONCURRENCY, 8);
|
|
90
|
+
const entries = [];
|
|
91
|
+
let filesHashed = 0;
|
|
92
|
+
for (let i = 0; i < filteredPaths.length; i += concurrency) {
|
|
93
|
+
assertNotAborted(signal);
|
|
94
|
+
const batch = filteredPaths.slice(i, i + concurrency);
|
|
95
|
+
const batchResults = await Promise.all(batch.map(async ({ filePath, relativePath }) => {
|
|
96
|
+
const fileHash = await hashFile(filePath, undefined, signal);
|
|
97
|
+
return { path: relativePath, hash: fileHash };
|
|
98
|
+
}));
|
|
99
|
+
entries.push(...batchResults);
|
|
100
|
+
filesHashed += batchResults.length;
|
|
93
101
|
reportHashProgress(onProgress, filesHashed);
|
|
94
102
|
}
|
|
95
103
|
reportHashProgress(onProgress, filesHashed, true);
|
|
@@ -139,43 +147,33 @@ async function handleCalculateHash(args, signal, onProgress) {
|
|
|
139
147
|
}
|
|
140
148
|
}
|
|
141
149
|
export function registerCalculateHashTool(server, options = {}) {
|
|
142
|
-
const handler = (args, extra) =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
151
|
+
toolName: 'calculate_hash',
|
|
152
|
+
extra,
|
|
153
|
+
timedSignal: {},
|
|
154
|
+
context: { path: args.path },
|
|
155
|
+
run: async (signal) => {
|
|
156
|
+
notifyProgress(extra, {
|
|
157
|
+
current: 0,
|
|
158
|
+
message: `🕮 calculate_hash: ${path.basename(args.path)}`,
|
|
159
|
+
});
|
|
149
160
|
const result = await handleCalculateHash(args, signal, createProgressReporter(extra));
|
|
150
161
|
const sc = result.structuredContent;
|
|
151
162
|
const totalFiles = sc.ok ? (sc.fileCount ?? 1) : 1;
|
|
152
163
|
const finalCurrent = totalFiles + 1;
|
|
153
|
-
const suffix = sc.ok
|
|
154
|
-
? `${(sc.hash ?? '').slice(0, 8)}...`
|
|
155
|
-
: 'failed';
|
|
164
|
+
const suffix = sc.ok ? `${(sc.hash ?? '').slice(0, 8)}...` : 'failed';
|
|
156
165
|
notifyProgress(extra, {
|
|
157
166
|
current: finalCurrent,
|
|
158
167
|
message: `🕮 calculate_hash: ${path.basename(args.path)} ➟ ${suffix}`,
|
|
159
168
|
});
|
|
160
169
|
return result;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
}, (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path)), { path: args.path });
|
|
170
|
+
},
|
|
171
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
172
|
+
});
|
|
166
173
|
const wrappedHandler = wrapToolHandler(handler, {
|
|
167
174
|
guard: options.isInitialized,
|
|
168
175
|
});
|
|
169
|
-
|
|
170
|
-
? { guard: options.isInitialized }
|
|
171
|
-
: undefined;
|
|
172
|
-
const tasks = getExperimentalTaskRegistration(server);
|
|
173
|
-
if (tasks?.registerToolTask) {
|
|
174
|
-
tasks.registerToolTask('calculate_hash', withDefaultIcons({
|
|
175
|
-
...CALCULATE_HASH_TOOL,
|
|
176
|
-
execution: { taskSupport: 'optional' },
|
|
177
|
-
}, options.iconInfo), createToolTaskHandler(wrappedHandler, taskOptions));
|
|
176
|
+
if (registerToolTaskIfAvailable(server, 'calculate_hash', CALCULATE_HASH_TOOL, wrappedHandler, options.iconInfo, options.isInitialized))
|
|
178
177
|
return;
|
|
179
|
-
}
|
|
180
178
|
server.registerTool('calculate_hash', withDefaultIcons({ ...CALCULATE_HASH_TOOL }, options.iconInfo), wrappedHandler);
|
|
181
179
|
}
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
4
|
-
import {
|
|
5
|
-
import { withToolDiagnostics } from '../lib/observability.js';
|
|
4
|
+
import { withAbort } from '../lib/fs-helpers.js';
|
|
6
5
|
import { validatePathForWrite } from '../lib/path-validation.js';
|
|
7
6
|
import { CreateDirectoryInputSchema, CreateDirectoryOutputSchema, } from '../schemas.js';
|
|
8
|
-
import { buildToolErrorResponse, buildToolResponse,
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, IDEMPOTENT_WRITE_TOOL_ANNOTATIONS, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
8
|
+
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
9
|
const CREATE_DIRECTORY_TOOL = {
|
|
10
10
|
title: 'Create Directory',
|
|
11
11
|
description: 'Create a new directory at the specified path (recursive)',
|
|
12
12
|
inputSchema: CreateDirectoryInputSchema,
|
|
13
13
|
outputSchema: CreateDirectoryOutputSchema,
|
|
14
|
-
annotations:
|
|
15
|
-
readOnlyHint: false,
|
|
16
|
-
idempotentHint: true,
|
|
17
|
-
openWorldHint: false,
|
|
18
|
-
},
|
|
14
|
+
annotations: IDEMPOTENT_WRITE_TOOL_ANNOTATIONS,
|
|
19
15
|
};
|
|
20
16
|
async function handleCreateDirectory(args, signal) {
|
|
21
17
|
const validPath = await validatePathForWrite(args.path, signal);
|
|
@@ -26,17 +22,19 @@ async function handleCreateDirectory(args, signal) {
|
|
|
26
22
|
});
|
|
27
23
|
}
|
|
28
24
|
export function registerCreateDirectoryTool(server, options = {}) {
|
|
29
|
-
const handler = (args, extra) =>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
server.registerTool('mkdir', withDefaultIcons({ ...CREATE_DIRECTORY_TOOL }, options.iconInfo), wrapToolHandler(handler, {
|
|
25
|
+
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
26
|
+
toolName: 'mkdir',
|
|
27
|
+
extra,
|
|
28
|
+
timedSignal: {},
|
|
29
|
+
context: { path: args.path },
|
|
30
|
+
run: (signal) => handleCreateDirectory(args, signal),
|
|
31
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
32
|
+
});
|
|
33
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
39
34
|
guard: options.isInitialized,
|
|
40
35
|
progressMessage: (args) => `🛠 mkdir: ${path.basename(args.path)}`,
|
|
41
|
-
})
|
|
36
|
+
});
|
|
37
|
+
if (registerToolTaskIfAvailable(server, 'mkdir', CREATE_DIRECTORY_TOOL, wrappedHandler, options.iconInfo, options.isInitialized))
|
|
38
|
+
return;
|
|
39
|
+
server.registerTool('mkdir', withDefaultIcons({ ...CREATE_DIRECTORY_TOOL }, options.iconInfo), wrappedHandler);
|
|
42
40
|
}
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { ErrorCode, isNodeError } from '../lib/errors.js';
|
|
4
|
-
import {
|
|
5
|
-
import { withToolDiagnostics } from '../lib/observability.js';
|
|
4
|
+
import { withAbort } from '../lib/fs-helpers.js';
|
|
6
5
|
import { validatePathForWrite } from '../lib/path-validation.js';
|
|
7
6
|
import { DeleteFileInputSchema, DeleteFileOutputSchema } from '../schemas.js';
|
|
8
|
-
import { buildToolErrorResponse, buildToolResponse,
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
8
|
+
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
9
|
const DELETE_FILE_TOOL = {
|
|
10
10
|
title: 'Delete File',
|
|
11
11
|
description: 'Delete a file or directory.',
|
|
12
12
|
inputSchema: DeleteFileInputSchema,
|
|
13
13
|
outputSchema: DeleteFileOutputSchema,
|
|
14
|
-
annotations:
|
|
15
|
-
readOnlyHint: false,
|
|
16
|
-
destructiveHint: true,
|
|
17
|
-
openWorldHint: false,
|
|
18
|
-
},
|
|
14
|
+
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
19
15
|
};
|
|
20
16
|
async function handleDeleteFile(args, signal) {
|
|
21
17
|
const validPath = await validatePathForWrite(args.path, signal);
|
|
@@ -51,36 +47,38 @@ async function handleDeleteFile(args, signal) {
|
|
|
51
47
|
});
|
|
52
48
|
}
|
|
53
49
|
export function registerDeleteFileTool(server, options = {}) {
|
|
54
|
-
const handler = (args, extra) =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
50
|
+
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
51
|
+
toolName: 'rm',
|
|
52
|
+
extra,
|
|
53
|
+
timedSignal: {},
|
|
54
|
+
context: { path: args.path },
|
|
55
|
+
run: (signal) => handleDeleteFile(args, signal),
|
|
56
|
+
onError: (error) => {
|
|
57
|
+
if (isNodeError(error)) {
|
|
58
|
+
if (error.code === 'ENOENT') {
|
|
59
|
+
return buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, args.path);
|
|
60
|
+
}
|
|
61
|
+
if (error.code === 'ENOTEMPTY') {
|
|
62
|
+
return buildToolErrorResponse(new Error(`Directory is not empty: ${args.path}. Use recursive: true to delete non-empty directories.`), ErrorCode.E_INVALID_INPUT, args.path);
|
|
63
|
+
}
|
|
64
|
+
if (error.code === 'EISDIR') {
|
|
65
|
+
return buildToolErrorResponse(new Error(`Path is a directory: ${args.path}. Use recursive: true to delete directories.`), ErrorCode.E_INVALID_INPUT, args.path);
|
|
66
|
+
}
|
|
67
|
+
if (error.code === 'EEXIST') {
|
|
68
|
+
return buildToolErrorResponse(new Error(`Directory is not empty: ${args.path}. Use recursive: true to delete non-empty directories.`), ErrorCode.E_INVALID_INPUT, args.path);
|
|
69
|
+
}
|
|
70
|
+
if (error.code === 'EPERM' || error.code === 'EACCES') {
|
|
71
|
+
return buildToolErrorResponse(error, ErrorCode.E_PERMISSION_DENIED, args.path);
|
|
72
|
+
}
|
|
75
73
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path);
|
|
81
|
-
}), { path: args.path });
|
|
82
|
-
server.registerTool('rm', withDefaultIcons({ ...DELETE_FILE_TOOL }, options.iconInfo), wrapToolHandler(handler, {
|
|
74
|
+
return buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path);
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
83
78
|
guard: options.isInitialized,
|
|
84
79
|
progressMessage: (args) => `🛠 rm: ${path.basename(args.path)}`,
|
|
85
|
-
})
|
|
80
|
+
});
|
|
81
|
+
if (registerToolTaskIfAvailable(server, 'rm', DELETE_FILE_TOOL, wrappedHandler, options.iconInfo, options.isInitialized))
|
|
82
|
+
return;
|
|
83
|
+
server.registerTool('rm', withDefaultIcons({ ...DELETE_FILE_TOOL }, options.iconInfo), wrappedHandler);
|
|
86
84
|
}
|
package/dist/tools/diff-files.js
CHANGED
|
@@ -3,21 +3,18 @@ import * as path from 'node:path';
|
|
|
3
3
|
import { createTwoFilesPatch } from 'diff';
|
|
4
4
|
import { MAX_TEXT_FILE_SIZE } from '../lib/constants.js';
|
|
5
5
|
import { ErrorCode, McpError } from '../lib/errors.js';
|
|
6
|
-
import {
|
|
7
|
-
import { withToolDiagnostics } from '../lib/observability.js';
|
|
6
|
+
import { withAbort } from '../lib/fs-helpers.js';
|
|
8
7
|
import { validateExistingPath } from '../lib/path-validation.js';
|
|
9
8
|
import { DiffFilesInputSchema, DiffFilesOutputSchema } from '../schemas.js';
|
|
10
|
-
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, maybeExternalizeTextContent,
|
|
9
|
+
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
11
10
|
const DIFF_FILES_TOOL = {
|
|
12
11
|
title: 'Diff Files',
|
|
13
|
-
description: 'Generate a unified diff between two files.'
|
|
12
|
+
description: 'Generate a unified diff between two files. ' +
|
|
13
|
+
'Output feeds directly into `apply_patch`. ' +
|
|
14
|
+
'Check `isIdentical` in the response — if true, the files are already in sync and no patch is needed.',
|
|
14
15
|
inputSchema: DiffFilesInputSchema,
|
|
15
16
|
outputSchema: DiffFilesOutputSchema,
|
|
16
|
-
annotations:
|
|
17
|
-
readOnlyHint: true,
|
|
18
|
-
idempotentHint: true,
|
|
19
|
-
openWorldHint: false,
|
|
20
|
-
},
|
|
17
|
+
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
21
18
|
};
|
|
22
19
|
function assertDiffFileSizeWithinLimit(filePath, size, maxFileSize) {
|
|
23
20
|
if (size <= maxFileSize)
|
|
@@ -25,7 +22,7 @@ function assertDiffFileSizeWithinLimit(filePath, size, maxFileSize) {
|
|
|
25
22
|
throw new McpError(ErrorCode.E_TOO_LARGE, `File too large for diff: ${filePath} (${size} bytes > ${maxFileSize} bytes).`, filePath, { size, maxFileSize });
|
|
26
23
|
}
|
|
27
24
|
async function handleDiffFiles(args, signal, resourceStore) {
|
|
28
|
-
const maxFileSize =
|
|
25
|
+
const maxFileSize = MAX_TEXT_FILE_SIZE;
|
|
29
26
|
const [originalPath, modifiedPath] = await Promise.all([
|
|
30
27
|
validateExistingPath(args.original, signal),
|
|
31
28
|
validateExistingPath(args.modified, signal),
|
|
@@ -75,15 +72,14 @@ async function handleDiffFiles(args, signal, resourceStore) {
|
|
|
75
72
|
]);
|
|
76
73
|
}
|
|
77
74
|
export function registerDiffFilesTool(server, options = {}) {
|
|
78
|
-
const handler = (args, extra) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}, (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.original)), { path: args.original });
|
|
75
|
+
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
76
|
+
toolName: 'diff_files',
|
|
77
|
+
extra,
|
|
78
|
+
timedSignal: {},
|
|
79
|
+
context: { path: args.original },
|
|
80
|
+
run: (signal) => handleDiffFiles(args, signal, options.resourceStore),
|
|
81
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.original),
|
|
82
|
+
});
|
|
87
83
|
server.registerTool('diff_files', withDefaultIcons({ ...DIFF_FILES_TOOL }, options.iconInfo), wrapToolHandler(handler, {
|
|
88
84
|
guard: options.isInitialized,
|
|
89
85
|
progressMessage: (args) => {
|
package/dist/tools/edit-file.js
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
4
|
-
import { atomicWriteFile
|
|
5
|
-
import { withToolDiagnostics } from '../lib/observability.js';
|
|
4
|
+
import { atomicWriteFile } from '../lib/fs-helpers.js';
|
|
6
5
|
import { validateExistingPath } from '../lib/path-validation.js';
|
|
7
6
|
import { EditFileInputSchema, EditFileOutputSchema } from '../schemas.js';
|
|
8
|
-
import { buildToolErrorResponse, buildToolResponse,
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
9
8
|
const EDIT_FILE_TOOL = {
|
|
10
9
|
title: 'Edit File',
|
|
11
10
|
description: 'Edit a file by replacing text. Sequentially applies a list of string replacements. ' +
|
|
12
|
-
'Replaces the first occurrence of each `oldText`.'
|
|
11
|
+
'Replaces the first occurrence of each `oldText`. ' +
|
|
12
|
+
'`oldText` must match exactly — include 3–5 lines of surrounding context to uniquely target the location. ' +
|
|
13
|
+
'Use `dryRun: true` to validate edits before writing.',
|
|
13
14
|
inputSchema: EditFileInputSchema,
|
|
14
15
|
outputSchema: EditFileOutputSchema,
|
|
15
|
-
annotations:
|
|
16
|
-
readOnlyHint: false,
|
|
17
|
-
openWorldHint: false,
|
|
18
|
-
},
|
|
16
|
+
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
19
17
|
};
|
|
20
18
|
function applyEdits(content, edits) {
|
|
21
19
|
let newContent = content;
|
|
@@ -37,7 +35,7 @@ function applyEdits(content, edits) {
|
|
|
37
35
|
minLine = startLine;
|
|
38
36
|
if (maxLine === undefined || endLine > maxLine)
|
|
39
37
|
maxLine = endLine;
|
|
40
|
-
newContent = newContent.replace(edit.oldText, edit.newText);
|
|
38
|
+
newContent = newContent.replace(edit.oldText, () => edit.newText);
|
|
41
39
|
appliedEdits += 1;
|
|
42
40
|
}
|
|
43
41
|
const result = {
|
|
@@ -73,15 +71,14 @@ async function handleEditFile(args, signal) {
|
|
|
73
71
|
return buildToolResponse(message, structured);
|
|
74
72
|
}
|
|
75
73
|
export function registerEditFileTool(server, options = {}) {
|
|
76
|
-
const handler = (args, extra) =>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}, (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path)), { path: args.path });
|
|
74
|
+
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
75
|
+
toolName: 'edit',
|
|
76
|
+
extra,
|
|
77
|
+
timedSignal: {},
|
|
78
|
+
context: { path: args.path },
|
|
79
|
+
run: (signal) => handleEditFile(args, signal),
|
|
80
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path),
|
|
81
|
+
});
|
|
85
82
|
server.registerTool('edit', withDefaultIcons({ ...EDIT_FILE_TOOL }, options.iconInfo), wrapToolHandler(handler, {
|
|
86
83
|
guard: options.isInitialized,
|
|
87
84
|
progressMessage: (args) => {
|
|
@@ -3,9 +3,8 @@ import { formatOperationSummary, joinLines } from '../config.js';
|
|
|
3
3
|
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
|
-
import { withToolDiagnostics } from '../lib/observability.js';
|
|
7
6
|
import { ListDirectoryInputSchema, ListDirectoryOutputSchema, } from '../schemas.js';
|
|
8
|
-
import { buildToolErrorResponse, buildToolResponse, resolvePathOrRoot, withDefaultIcons,
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
9
8
|
const LIST_DIRECTORY_TOOL = {
|
|
10
9
|
title: 'List Directory',
|
|
11
10
|
description: 'List the immediate contents of a directory (non-recursive). ' +
|
|
@@ -15,11 +14,7 @@ const LIST_DIRECTORY_TOOL = {
|
|
|
15
14
|
'For recursive searches, use find instead.',
|
|
16
15
|
inputSchema: ListDirectoryInputSchema,
|
|
17
16
|
outputSchema: ListDirectoryOutputSchema,
|
|
18
|
-
annotations:
|
|
19
|
-
readOnlyHint: true,
|
|
20
|
-
idempotentHint: true,
|
|
21
|
-
openWorldHint: false,
|
|
22
|
-
},
|
|
17
|
+
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
23
18
|
};
|
|
24
19
|
function buildListTextResult(result) {
|
|
25
20
|
const { entries, summary, path } = result;
|
|
@@ -29,13 +24,11 @@ function buildListTextResult(result) {
|
|
|
29
24
|
}
|
|
30
25
|
return `${path} (no matches)`;
|
|
31
26
|
}
|
|
32
|
-
const lines = [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}),
|
|
38
|
-
];
|
|
27
|
+
const lines = [path];
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
const suffix = entry.type === 'directory' ? '/' : '';
|
|
30
|
+
lines.push(` ${entry.relativePath}${suffix}`);
|
|
31
|
+
}
|
|
39
32
|
let truncatedReason;
|
|
40
33
|
if (summary.truncated) {
|
|
41
34
|
if (summary.stoppedReason === 'maxEntries') {
|
|
@@ -62,20 +55,22 @@ function buildStructuredListEntry(entry) {
|
|
|
62
55
|
}
|
|
63
56
|
function buildStructuredListResult(result) {
|
|
64
57
|
const { entries, summary, path: resultPath } = result;
|
|
58
|
+
const structuredEntries = [];
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
structuredEntries.push(buildStructuredListEntry(entry));
|
|
61
|
+
}
|
|
65
62
|
return {
|
|
66
63
|
ok: true,
|
|
67
64
|
path: resultPath,
|
|
68
|
-
entries:
|
|
65
|
+
entries: structuredEntries,
|
|
69
66
|
totalEntries: summary.totalEntries,
|
|
70
|
-
truncated: summary.truncated,
|
|
71
|
-
entriesScanned: summary.entriesScanned,
|
|
72
|
-
entriesVisible: summary.entriesVisible,
|
|
67
|
+
...(summary.truncated ? { truncated: summary.truncated } : {}),
|
|
73
68
|
totalFiles: summary.totalFiles,
|
|
74
69
|
totalDirectories: summary.totalDirectories,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
...(summary.stoppedReason ? { stoppedReason: summary.stoppedReason } : {}),
|
|
71
|
+
...(summary.skippedInaccessible
|
|
72
|
+
? { skippedInaccessible: summary.skippedInaccessible }
|
|
73
|
+
: {}),
|
|
79
74
|
};
|
|
80
75
|
}
|
|
81
76
|
async function handleListDirectory(args, signal) {
|
|
@@ -94,7 +89,13 @@ async function handleListDirectory(args, signal) {
|
|
|
94
89
|
return buildToolResponse(buildListTextResult(result), buildStructuredListResult(result));
|
|
95
90
|
}
|
|
96
91
|
export function registerListDirectoryTool(server, options = {}) {
|
|
97
|
-
const handler = (args, extra) =>
|
|
92
|
+
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
93
|
+
toolName: 'ls',
|
|
94
|
+
extra,
|
|
95
|
+
context: { path: args.path ?? '.' },
|
|
96
|
+
run: (signal) => handleListDirectory(args, signal),
|
|
97
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, args.path ?? '.'),
|
|
98
|
+
});
|
|
98
99
|
server.registerTool('ls', withDefaultIcons({ ...LIST_DIRECTORY_TOOL }, options.iconInfo), wrapToolHandler(handler, {
|
|
99
100
|
guard: options.isInitialized,
|
|
100
101
|
progressMessage: (args) => {
|
package/dist/tools/move-file.js
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { ErrorCode, isNodeError } from '../lib/errors.js';
|
|
4
|
-
import {
|
|
5
|
-
import { withToolDiagnostics } from '../lib/observability.js';
|
|
4
|
+
import { withAbort } from '../lib/fs-helpers.js';
|
|
6
5
|
import { validateExistingPath, validatePathForWrite, } from '../lib/path-validation.js';
|
|
7
6
|
import { MoveFileInputSchema, MoveFileOutputSchema } from '../schemas.js';
|
|
8
|
-
import { buildToolErrorResponse, buildToolResponse,
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, wrapToolHandler, } from './shared.js';
|
|
8
|
+
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
9
|
const MOVE_FILE_TOOL = {
|
|
10
10
|
title: 'Move File',
|
|
11
11
|
description: 'Move or rename a file or directory.',
|
|
12
12
|
inputSchema: MoveFileInputSchema,
|
|
13
13
|
outputSchema: MoveFileOutputSchema,
|
|
14
|
-
annotations:
|
|
15
|
-
readOnlyHint: false,
|
|
16
|
-
destructiveHint: true,
|
|
17
|
-
openWorldHint: false,
|
|
18
|
-
},
|
|
14
|
+
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
19
15
|
};
|
|
20
16
|
async function handleMoveFile(args, signal) {
|
|
21
17
|
const validSource = await validateExistingPath(args.source, signal);
|
|
@@ -42,17 +38,19 @@ async function handleMoveFile(args, signal) {
|
|
|
42
38
|
});
|
|
43
39
|
}
|
|
44
40
|
export function registerMoveFileTool(server, options = {}) {
|
|
45
|
-
const handler = (args, extra) =>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
server.registerTool('mv', withDefaultIcons({ ...MOVE_FILE_TOOL }, options.iconInfo), wrapToolHandler(handler, {
|
|
41
|
+
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
42
|
+
toolName: 'mv',
|
|
43
|
+
extra,
|
|
44
|
+
timedSignal: {},
|
|
45
|
+
context: { path: args.source },
|
|
46
|
+
run: (signal) => handleMoveFile(args, signal),
|
|
47
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.source),
|
|
48
|
+
});
|
|
49
|
+
const wrappedHandler = wrapToolHandler(handler, {
|
|
55
50
|
guard: options.isInitialized,
|
|
56
51
|
progressMessage: (args) => `🛠 mv: ${path.basename(args.source)} ➟ ${path.basename(args.destination)}`,
|
|
57
|
-
})
|
|
52
|
+
});
|
|
53
|
+
if (registerToolTaskIfAvailable(server, 'mv', MOVE_FILE_TOOL, wrappedHandler, options.iconInfo, options.isInitialized))
|
|
54
|
+
return;
|
|
55
|
+
server.registerTool('mv', withDefaultIcons({ ...MOVE_FILE_TOOL }, options.iconInfo), wrappedHandler);
|
|
58
56
|
}
|