@j0hanz/filesystem-mcp 1.19.0 → 2.0.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 +333 -838
- package/dist/cli-help.d.ts +3 -0
- package/dist/cli-help.d.ts.map +1 -0
- package/dist/cli-help.js +169 -0
- package/dist/cli-help.js.map +1 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +168 -123
- package/dist/cli.js.map +1 -0
- package/dist/core/concurrency.d.ts +70 -0
- package/dist/core/concurrency.d.ts.map +1 -0
- package/dist/core/concurrency.js +173 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +11 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cursor.d.ts +23 -0
- package/dist/core/cursor.d.ts.map +1 -0
- package/dist/core/cursor.js +62 -0
- package/dist/core/cursor.js.map +1 -0
- package/dist/core/errors.d.ts +96 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +356 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/file-uri.d.ts +24 -0
- package/dist/core/file-uri.d.ts.map +1 -0
- package/dist/core/file-uri.js +52 -0
- package/dist/core/file-uri.js.map +1 -0
- package/dist/core/fmt.d.ts +38 -0
- package/dist/core/fmt.d.ts.map +1 -0
- package/dist/core/fmt.js +109 -0
- package/dist/core/fmt.js.map +1 -0
- package/dist/core/fs.d.ts +108 -0
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/fs.js +243 -0
- package/dist/core/fs.js.map +1 -0
- package/dist/core/glob.d.ts +41 -0
- package/dist/core/glob.d.ts.map +1 -0
- package/dist/core/glob.js +399 -0
- package/dist/core/glob.js.map +1 -0
- package/dist/core/input-required.d.ts +147 -0
- package/dist/core/input-required.d.ts.map +1 -0
- package/dist/core/input-required.js +261 -0
- package/dist/core/input-required.js.map +1 -0
- package/dist/core/mime.d.ts +15 -0
- package/dist/core/mime.d.ts.map +1 -0
- package/dist/core/mime.js +188 -0
- package/dist/core/mime.js.map +1 -0
- package/dist/core/observability.d.ts +10 -0
- package/dist/core/observability.d.ts.map +1 -0
- package/dist/core/observability.js +76 -0
- package/dist/core/observability.js.map +1 -0
- package/dist/core/page-store.d.ts +41 -0
- package/dist/core/page-store.d.ts.map +1 -0
- package/dist/core/page-store.js +74 -0
- package/dist/core/page-store.js.map +1 -0
- package/dist/core/path-completer.d.ts +10 -0
- package/dist/core/path-completer.d.ts.map +1 -0
- package/dist/core/path-completer.js +227 -0
- package/dist/core/path-completer.js.map +1 -0
- package/dist/core/path-discovery.d.ts +8 -0
- package/dist/core/path-discovery.d.ts.map +1 -0
- package/dist/core/path-discovery.js +105 -0
- package/dist/core/path-discovery.js.map +1 -0
- package/dist/core/path-utils.d.ts +9 -0
- package/dist/core/path-utils.d.ts.map +1 -0
- package/dist/core/path-utils.js +121 -0
- package/dist/core/path-utils.js.map +1 -0
- package/dist/core/path.d.ts +148 -0
- package/dist/core/path.d.ts.map +1 -0
- package/dist/core/path.js +674 -0
- package/dist/core/path.js.map +1 -0
- package/dist/core/primitives.d.ts +23 -0
- package/dist/core/primitives.d.ts.map +1 -0
- package/dist/core/primitives.js +54 -0
- package/dist/core/primitives.js.map +1 -0
- package/dist/core/read.d.ts +74 -0
- package/dist/core/read.d.ts.map +1 -0
- package/dist/core/read.js +562 -0
- package/dist/core/read.js.map +1 -0
- package/dist/core/schema.d.ts +139 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +345 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/search.d.ts +100 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +248 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/sensitive.d.ts +6 -0
- package/dist/core/sensitive.d.ts.map +1 -0
- package/dist/core/sensitive.js +142 -0
- package/dist/core/sensitive.js.map +1 -0
- package/dist/core/store.d.ts +57 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +162 -0
- package/dist/core/store.js.map +1 -0
- package/dist/core/util.d.ts +26 -0
- package/dist/core/util.d.ts.map +1 -0
- package/dist/core/util.js +66 -0
- package/dist/core/util.js.map +1 -0
- package/dist/core/watcher-registry.d.ts +68 -0
- package/dist/core/watcher-registry.d.ts.map +1 -0
- package/dist/core/watcher-registry.js +312 -0
- package/dist/core/watcher-registry.js.map +1 -0
- package/dist/http-policy.d.ts +121 -0
- package/dist/http-policy.d.ts.map +1 -0
- package/dist/http-policy.js +340 -0
- package/dist/http-policy.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -41
- package/dist/index.js.map +1 -0
- package/dist/instructions.d.ts +12 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +71 -0
- package/dist/instructions.js.map +1 -0
- package/dist/prompts.d.ts +8 -6
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +74 -168
- package/dist/prompts.js.map +1 -0
- package/dist/resources.d.ts +73 -9
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +392 -163
- package/dist/resources.js.map +1 -0
- package/dist/server.d.ts +62 -1
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +139 -1
- package/dist/server.js.map +1 -0
- package/dist/tools/batch.d.ts +89 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +157 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/create.d.ts +2 -0
- package/dist/tools/create.d.ts.map +1 -0
- package/dist/tools/create.js +142 -0
- package/dist/tools/create.js.map +1 -0
- package/dist/tools/define.d.ts +110 -0
- package/dist/tools/define.d.ts.map +1 -0
- package/dist/tools/define.js +394 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/delete-file.d.ts +2 -4
- package/dist/tools/delete-file.d.ts.map +1 -0
- package/dist/tools/delete-file.js +330 -87
- package/dist/tools/delete-file.js.map +1 -0
- package/dist/tools/diff.d.ts +2 -0
- package/dist/tools/diff.d.ts.map +1 -0
- package/dist/tools/diff.js +94 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +442 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/index.d.ts +26 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +52 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list.d.ts +2 -0
- package/dist/tools/list.d.ts.map +1 -0
- package/dist/tools/list.js +291 -0
- package/dist/tools/list.js.map +1 -0
- package/dist/tools/move.d.ts +2 -0
- package/dist/tools/move.d.ts.map +1 -0
- package/dist/tools/move.js +321 -0
- package/dist/tools/move.js.map +1 -0
- package/dist/tools/patch.d.ts +2 -0
- package/dist/tools/patch.d.ts.map +1 -0
- package/dist/tools/patch.js +155 -0
- package/dist/tools/patch.js.map +1 -0
- package/dist/tools/progress.d.ts +52 -0
- package/dist/tools/progress.d.ts.map +1 -0
- package/dist/tools/progress.js +172 -0
- package/dist/tools/progress.js.map +1 -0
- package/dist/tools/read.d.ts +2 -4
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +398 -152
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/replace-in-files.d.ts +2 -4
- package/dist/tools/replace-in-files.d.ts.map +1 -0
- package/dist/tools/replace-in-files.js +435 -264
- package/dist/tools/replace-in-files.js.map +1 -0
- package/dist/tools/roots.d.ts +2 -4
- package/dist/tools/roots.d.ts.map +1 -0
- package/dist/tools/roots.js +29 -52
- package/dist/tools/roots.js.map +1 -0
- package/dist/tools/search-content.d.ts +2 -4
- package/dist/tools/search-content.d.ts.map +1 -0
- package/dist/tools/search-content.js +262 -228
- package/dist/tools/search-content.js.map +1 -0
- package/dist/tools/search-files.d.ts +2 -4
- package/dist/tools/search-files.d.ts.map +1 -0
- package/dist/tools/search-files.js +162 -161
- package/dist/tools/search-files.js.map +1 -0
- package/dist/tools/stat.d.ts +2 -4
- package/dist/tools/stat.d.ts.map +1 -0
- package/dist/tools/stat.js +202 -62
- package/dist/tools/stat.js.map +1 -0
- package/dist/transport/http.d.ts +5 -0
- package/dist/transport/http.d.ts.map +1 -0
- package/dist/transport/http.js +276 -0
- package/dist/transport/http.js.map +1 -0
- package/dist/transport/shared.d.ts +45 -0
- package/dist/transport/shared.d.ts.map +1 -0
- package/dist/transport/shared.js +65 -0
- package/dist/transport/shared.js.map +1 -0
- package/dist/transport/stdio.d.ts +32 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +279 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport.d.ts +5 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +4 -0
- package/dist/transport.js.map +1 -0
- package/package.json +37 -46
- package/dist/assets/logo.svg +0 -35
- package/dist/completions.d.ts +0 -2
- package/dist/completions.js +0 -572
- package/dist/config.d.ts +0 -119
- package/dist/config.js +0 -31
- package/dist/lib/abort.d.ts +0 -7
- package/dist/lib/abort.js +0 -81
- package/dist/lib/constants.d.ts +0 -42
- package/dist/lib/constants.js +0 -313
- package/dist/lib/errors.d.ts +0 -28
- package/dist/lib/errors.js +0 -301
- package/dist/lib/file-operations/core.d.ts +0 -53
- package/dist/lib/file-operations/core.js +0 -144
- package/dist/lib/file-operations/metadata.d.ts +0 -77
- package/dist/lib/file-operations/metadata.js +0 -878
- package/dist/lib/file-operations/search.d.ts +0 -46
- package/dist/lib/file-operations/search.js +0 -1194
- package/dist/lib/file-operations/traversal.d.ts +0 -40
- package/dist/lib/file-operations/traversal.js +0 -310
- package/dist/lib/fs-helpers.d.ts +0 -46
- package/dist/lib/fs-helpers.js +0 -606
- package/dist/lib/globs.d.ts +0 -2
- package/dist/lib/globs.js +0 -19
- package/dist/lib/logger.d.ts +0 -27
- package/dist/lib/logger.js +0 -91
- package/dist/lib/observability.d.ts +0 -33
- package/dist/lib/observability.js +0 -373
- package/dist/lib/paths.d.ts +0 -35
- package/dist/lib/paths.js +0 -634
- package/dist/lib/resource-store.d.ts +0 -28
- package/dist/lib/resource-store.js +0 -195
- package/dist/lib/utils.d.ts +0 -7
- package/dist/lib/utils.js +0 -37
- package/dist/lib/zod-codecs.d.ts +0 -2
- package/dist/lib/zod-codecs.js +0 -18
- package/dist/pkg-info.d.ts +0 -7
- package/dist/pkg-info.js +0 -9
- package/dist/resources/generated-instructions.d.ts +0 -1
- package/dist/resources/generated-instructions.js +0 -105
- package/dist/resources/tool-catalog.d.ts +0 -2
- package/dist/resources/tool-catalog.js +0 -78
- package/dist/resources/tool-info.d.ts +0 -9
- package/dist/resources/tool-info.js +0 -206
- package/dist/resources/workflows.d.ts +0 -1
- package/dist/resources/workflows.js +0 -72
- package/dist/schemas.d.ts +0 -505
- package/dist/schemas.js +0 -676
- package/dist/server/bootstrap.d.ts +0 -6
- package/dist/server/bootstrap.js +0 -563
- package/dist/server/event-store.d.ts +0 -18
- package/dist/server/event-store.js +0 -71
- package/dist/server/roots-manager.d.ts +0 -28
- package/dist/server/roots-manager.js +0 -227
- package/dist/server/task-store.d.ts +0 -10
- package/dist/server/task-store.js +0 -91
- package/dist/tools/apply-patch.d.ts +0 -4
- package/dist/tools/apply-patch.js +0 -235
- package/dist/tools/calculate-hash.d.ts +0 -4
- package/dist/tools/calculate-hash.js +0 -187
- package/dist/tools/contract.d.ts +0 -50
- package/dist/tools/contract.js +0 -1
- package/dist/tools/create-directory.d.ts +0 -4
- package/dist/tools/create-directory.js +0 -68
- package/dist/tools/diff-files.d.ts +0 -4
- package/dist/tools/diff-files.js +0 -136
- package/dist/tools/edit-file.d.ts +0 -4
- package/dist/tools/edit-file.js +0 -256
- package/dist/tools/icons.d.ts +0 -15
- package/dist/tools/icons.js +0 -24
- package/dist/tools/list-directory.d.ts +0 -4
- package/dist/tools/list-directory.js +0 -218
- package/dist/tools/move-file.d.ts +0 -4
- package/dist/tools/move-file.js +0 -178
- package/dist/tools/read-multiple.d.ts +0 -4
- package/dist/tools/read-multiple.js +0 -207
- package/dist/tools/shared.d.ts +0 -172
- package/dist/tools/shared.js +0 -599
- package/dist/tools/stat-many.d.ts +0 -4
- package/dist/tools/stat-many.js +0 -104
- package/dist/tools/task-support.d.ts +0 -29
- package/dist/tools/task-support.js +0 -472
- package/dist/tools/tree.d.ts +0 -4
- package/dist/tools/tree.js +0 -102
- package/dist/tools/write-file.d.ts +0 -4
- package/dist/tools/write-file.js +0 -60
- package/dist/tools.d.ts +0 -5
- package/dist/tools.js +0 -54
|
@@ -1,101 +1,344 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isInputRequiredResult } from '@modelcontextprotocol/server';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
3
|
+
import * as z from 'zod/v4';
|
|
4
|
+
import { processInParallel } from '../core/concurrency.js';
|
|
5
|
+
import { ErrorCode, FsError, isFsError, isNodeError, isNotFoundErrno, Problem, } from '../core/errors.js';
|
|
6
|
+
import { joinRoster, pathLabel } from '../core/fmt.js';
|
|
7
|
+
import { getFileType } from '../core/fs.js';
|
|
8
|
+
import { choiceInput, pendingRoundTrip, readAcceptedChoice } from '../core/input-required.js';
|
|
9
|
+
import { defaultFalseBoolean, OperationSummarySchema, PerFileErrorSchema, RequiredPath, } from '../core/schema.js';
|
|
10
|
+
import { PARALLEL_CONCURRENCY } from '../core/util.js';
|
|
11
|
+
import { defineTool } from './define.js';
|
|
12
|
+
const DeleteInputSchema = z.strictObject({
|
|
13
|
+
paths: z
|
|
14
|
+
.array(RequiredPath)
|
|
15
|
+
.min(1)
|
|
16
|
+
.max(1000)
|
|
17
|
+
.describe('Paths to delete (max 1000); accepts files, directories, or symlinks'),
|
|
18
|
+
recursive: defaultFalseBoolean('Delete directory contents recursively (required for non-empty directories)'),
|
|
19
|
+
ignoreIfNotExists: defaultFalseBoolean('Silently succeed if a path does not exist instead of returning an error'),
|
|
20
|
+
});
|
|
21
|
+
const DeletePerPathValueSchema = z.strictObject({
|
|
22
|
+
deleted: z.boolean().describe('True when the path was removed; false when the user chose Skip'),
|
|
23
|
+
});
|
|
24
|
+
const DeletePerPathSchema = z.strictObject({
|
|
25
|
+
path: z.string().describe('Requested path'),
|
|
26
|
+
value: DeletePerPathValueSchema.optional().describe('Delete outcome; present on success'),
|
|
27
|
+
error: PerFileErrorSchema.optional().describe('Error details; present on failure'),
|
|
28
|
+
});
|
|
29
|
+
// One envelope for every batch tool: `read` and `stat` already answer with
|
|
30
|
+
// `{ results, summary }`, so `delete` does too. The old shape switched between
|
|
31
|
+
// `{ ok, path }` and `{ ok: false, failures }` depending on the outcome, which
|
|
32
|
+
// left a caller parsing two schemas for one tool and never named the paths in a
|
|
33
|
+
// multi-delete's text.
|
|
34
|
+
const DeleteOutputSchema = z.strictObject({
|
|
35
|
+
results: z
|
|
36
|
+
.array(DeletePerPathSchema)
|
|
37
|
+
.describe('Per-path results ordered to match the input paths'),
|
|
38
|
+
summary: OperationSummarySchema,
|
|
39
|
+
});
|
|
40
|
+
const ERR_NOT_EMPTY = new Error('Directory not empty. Set recursive: true.');
|
|
41
|
+
function toDeleteFailure(path, error) {
|
|
42
|
+
// A missing path reached here as the raw errno text ("ENOENT: no such file or
|
|
43
|
+
// directory, lstat 'c:\...'"), while `stat` reported the same condition as
|
|
44
|
+
// "Path not found". One condition, one message — and the syscall name stays
|
|
45
|
+
// off the wire.
|
|
46
|
+
if (isNotFoundErrno(error)) {
|
|
47
|
+
return {
|
|
48
|
+
path,
|
|
49
|
+
error: Problem.toPerFileError(new FsError(ErrorCode.NOT_FOUND, 'Path not found', path), ErrorCode.NOT_FOUND, path),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (isNodeError(error) &&
|
|
53
|
+
(error.code === 'ENOTEMPTY' || error.code === 'EISDIR' || error.code === 'EEXIST')) {
|
|
54
|
+
return {
|
|
55
|
+
path,
|
|
56
|
+
error: Problem.toPerFileError(ERR_NOT_EMPTY, ErrorCode.INVALID_INPUT, path),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return { path, error: Problem.toPerFileError(error, ErrorCode.UNKNOWN, path) };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Phase 1 (no mutation): validate, stat, and decide whether a path needs a
|
|
63
|
+
* confirmation before anything is deleted. A pending path is a recursive
|
|
64
|
+
* non-empty directory; everything else is ready to delete directly.
|
|
65
|
+
*/
|
|
66
|
+
async function planPath(inputPath, args, fs) {
|
|
67
|
+
let validPath;
|
|
28
68
|
try {
|
|
29
|
-
|
|
69
|
+
validPath = await fs.pathGuard.validatePathForDelete(inputPath);
|
|
30
70
|
}
|
|
31
71
|
catch (error) {
|
|
32
|
-
if (
|
|
33
|
-
error.code ===
|
|
34
|
-
|
|
35
|
-
return buildToolResponse(`Successfully deleted: ${args.path}`, {
|
|
36
|
-
ok: true,
|
|
37
|
-
path: validPath,
|
|
38
|
-
});
|
|
72
|
+
if (args.ignoreIfNotExists &&
|
|
73
|
+
((isFsError(error) && error.code === ErrorCode.NOT_FOUND) || isNotFoundErrno(error))) {
|
|
74
|
+
return { status: 'noop', item: { path: inputPath } };
|
|
39
75
|
}
|
|
40
|
-
|
|
76
|
+
return { status: 'fail', failure: toDeleteFailure(inputPath, error) };
|
|
77
|
+
}
|
|
78
|
+
if (fs.pathGuard.isAllowedRoot(validPath)) {
|
|
79
|
+
return {
|
|
80
|
+
status: 'fail',
|
|
81
|
+
// Keyed by the REQUESTED path, like every other failure here: handleDelete
|
|
82
|
+
// orders results by input path, and a resolved key would not be found for
|
|
83
|
+
// a relative request.
|
|
84
|
+
failure: {
|
|
85
|
+
path: inputPath,
|
|
86
|
+
error: Problem.accessDenied('Deleting a workspace root directory is not allowed', {
|
|
87
|
+
path: validPath,
|
|
88
|
+
}),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
41
91
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
92
|
+
let firstStats;
|
|
93
|
+
try {
|
|
94
|
+
firstStats = await fs.lstat(validPath);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (isNotFoundErrno(error) && args.ignoreIfNotExists) {
|
|
98
|
+
return { status: 'noop', item: { path: inputPath } };
|
|
99
|
+
}
|
|
100
|
+
return { status: 'fail', failure: toDeleteFailure(inputPath, error) };
|
|
101
|
+
}
|
|
102
|
+
const itemType = getFileType(firstStats.stats);
|
|
103
|
+
let hasChildren = false;
|
|
104
|
+
if (args.recursive && itemType === 'directory') {
|
|
105
|
+
try {
|
|
106
|
+
hasChildren = await fs.hasChildrenUnchecked(validPath);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return { status: 'fail', failure: toDeleteFailure(inputPath, error) };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const pending = hasChildren;
|
|
113
|
+
return { status: 'plan', plan: { inputPath, validPath, itemType, firstStats, pending } };
|
|
114
|
+
}
|
|
115
|
+
async function performDeletion(validPath, args, isDirectory, fsOps) {
|
|
116
|
+
if (isDirectory && !args.recursive) {
|
|
117
|
+
await fsOps.rmdir(validPath);
|
|
46
118
|
}
|
|
47
119
|
else {
|
|
48
|
-
await
|
|
120
|
+
await fsOps.rm(validPath, {
|
|
49
121
|
recursive: args.recursive,
|
|
50
122
|
force: args.ignoreIfNotExists,
|
|
51
|
-
})
|
|
123
|
+
});
|
|
52
124
|
}
|
|
53
|
-
Logger.info(`rm: ${args.path}`);
|
|
54
|
-
return buildToolResponse(`Successfully deleted: ${args.path}`, {
|
|
55
|
-
ok: true,
|
|
56
|
-
path: validPath,
|
|
57
|
-
});
|
|
58
125
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Phase 2 (mutation): TOCTOU re-stat against the plan's first stat, then
|
|
128
|
+
* delete. A type or identity change across the confirmation gap fails closed
|
|
129
|
+
* rather than deleting a replacement object the user never confirmed.
|
|
130
|
+
*/
|
|
131
|
+
async function finalizeDeletion(plan, args, ctx) {
|
|
132
|
+
let currentStats;
|
|
133
|
+
try {
|
|
134
|
+
currentStats = await ctx.fs.lstat(plan.validPath);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (isNotFoundErrno(error) && args.ignoreIfNotExists) {
|
|
138
|
+
return { item: { path: plan.validPath } };
|
|
139
|
+
}
|
|
140
|
+
return { failure: toDeleteFailure(plan.inputPath, error) };
|
|
141
|
+
}
|
|
142
|
+
const currentItemType = getFileType(currentStats.stats);
|
|
143
|
+
// Identity comparison, not just the coarse category: a swap during the
|
|
144
|
+
// confirmation gap (delete original, create a same-named replacement) keeps
|
|
145
|
+
// the type but changes dev/ino/birthtimeMs. birthtimeMs is the primary
|
|
146
|
+
// cross-platform signal — dev/ino can read 0 on some non-POSIX drivers — so
|
|
147
|
+
// combine all three rather than trusting dev/ino alone.
|
|
148
|
+
const identityChanged = plan.firstStats.stats.dev !== currentStats.stats.dev ||
|
|
149
|
+
plan.firstStats.stats.ino !== currentStats.stats.ino ||
|
|
150
|
+
plan.firstStats.stats.birthtimeMs !== currentStats.stats.birthtimeMs;
|
|
151
|
+
if (plan.itemType !== 'other' && (currentItemType !== plan.itemType || identityChanged)) {
|
|
152
|
+
return {
|
|
153
|
+
failure: {
|
|
154
|
+
path: plan.validPath,
|
|
155
|
+
error: Problem.invalidInput(`Delete failed: item type changed from ${plan.itemType} to ${currentItemType} during confirmation.`, { path: plan.validPath }),
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
await performDeletion(plan.validPath, args, currentStats.stats.isDirectory(), ctx.fs);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
return { failure: toDeleteFailure(plan.inputPath, error) };
|
|
164
|
+
}
|
|
165
|
+
ctx.log?.('info', `rm: ${plan.inputPath}`, 'delete');
|
|
166
|
+
return { item: { path: plan.validPath } };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Execute one planned path on the retry round. A pending path deletes only on
|
|
170
|
+
* an accepted `confirm: true`; decline, cancel, or a missing key report
|
|
171
|
+
* `CANCELLED` for that path (R3 proceed, R4/R5 cancelled). A non-pending path
|
|
172
|
+
* deletes directly (R14: non-pending items proceed alongside accepted ones).
|
|
173
|
+
*/
|
|
174
|
+
async function executePlan(plan, args, ctx, pendingSorted) {
|
|
175
|
+
if (plan.pending) {
|
|
176
|
+
const key = `confirm_${pendingSorted.indexOf(plan.validPath)}`;
|
|
177
|
+
const choice = readAcceptedChoice(ctx.inputResponses, key);
|
|
178
|
+
if (choice === 'skip') {
|
|
179
|
+
return { skipped: true, path: plan.validPath };
|
|
180
|
+
}
|
|
181
|
+
if (choice !== 'delete') {
|
|
182
|
+
return {
|
|
183
|
+
failure: {
|
|
184
|
+
path: plan.validPath,
|
|
185
|
+
error: Problem.cancelled('Delete cancelled: confirmation was declined or missing', {
|
|
186
|
+
path: plan.validPath,
|
|
187
|
+
}),
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return finalizeDeletion(plan, args, ctx);
|
|
193
|
+
}
|
|
194
|
+
async function handleDelete(args, ctx) {
|
|
195
|
+
// Duplicate paths collapse: results are keyed by path, so a repeated entry
|
|
196
|
+
// would emit two rows for one deletion and inflate `summary.total`.
|
|
197
|
+
const paths = [...new Set(args.paths)];
|
|
198
|
+
// Phase 1 (no mutation): plan every path.
|
|
199
|
+
const planned = await processInParallel(paths, (inputPath) => planPath(inputPath, args, ctx.fs), PARALLEL_CONCURRENCY, ctx.signal);
|
|
200
|
+
const plans = [];
|
|
201
|
+
const earlyFailures = [];
|
|
202
|
+
const earlyNoop = [];
|
|
203
|
+
for (const { value: r } of planned.results) {
|
|
204
|
+
if (r.status === 'fail')
|
|
205
|
+
earlyFailures.push(r.failure);
|
|
206
|
+
else if (r.status === 'noop')
|
|
207
|
+
earlyNoop.push(r.item);
|
|
208
|
+
else
|
|
209
|
+
plans.push(r.plan);
|
|
210
|
+
}
|
|
211
|
+
for (const { index, error } of planned.errors) {
|
|
212
|
+
const path = paths[index] ?? '(unknown)';
|
|
213
|
+
earlyFailures.push({ path, error: { code: ErrorCode.UNKNOWN, message: error.message } });
|
|
214
|
+
}
|
|
215
|
+
// Pending set: recursive + non-empty directories, sorted and de-duplicated.
|
|
216
|
+
// `buildInputRequired` seals exactly this set into the requestState, and the
|
|
217
|
+
// retried round recomputes it from the same args so a swapped retry (accept
|
|
218
|
+
// for X, retry with Y) is rejected — the codec only proves the state was not
|
|
219
|
+
// tampered, not that it matches the current request (R9).
|
|
220
|
+
const pendingSorted = [...new Set(plans.filter((p) => p.pending).map((p) => p.validPath))].sort();
|
|
221
|
+
if (pendingSorted.length > 0) {
|
|
222
|
+
// Round 1 returns input_required (atomic — R14: nothing deleted yet, not
|
|
223
|
+
// even the non-pending items in the same call); a retry whose verified
|
|
224
|
+
// state does not bind this pending set throws (R9) via `pendingRoundTrip`.
|
|
225
|
+
const round = await pendingRoundTrip({
|
|
226
|
+
op: 'delete',
|
|
227
|
+
pending: pendingSorted,
|
|
228
|
+
requestState: ctx.requestState,
|
|
229
|
+
clientCapabilities: ctx.clientCapabilities,
|
|
230
|
+
buildInputs: (ps) => ps.map((p, i) => choiceInput(`confirm_${i}`, `Permanently delete "${p}" and all its contents? This cannot be undone.`, [
|
|
231
|
+
{ value: 'delete', title: 'Delete' },
|
|
232
|
+
{ value: 'skip', title: 'Skip' },
|
|
233
|
+
])),
|
|
234
|
+
});
|
|
235
|
+
if (round !== undefined)
|
|
236
|
+
return round;
|
|
237
|
+
}
|
|
238
|
+
// Phase 2 (mutation): execute deletions for every planned path.
|
|
239
|
+
const executed = await processInParallel(plans, (plan) => executePlan(plan, args, ctx, pendingSorted), PARALLEL_CONCURRENCY, ctx.signal);
|
|
240
|
+
// Collect by path first, then emit in the caller's input order so
|
|
241
|
+
// `results[i]` lines up with `paths[i]` the way read/stat guarantee.
|
|
242
|
+
const byPath = new Map();
|
|
243
|
+
const record = (path, entry) => {
|
|
244
|
+
byPath.set(path, entry);
|
|
245
|
+
};
|
|
246
|
+
for (const n of earlyNoop)
|
|
247
|
+
record(n.path, { path: n.path, value: { deleted: true } });
|
|
248
|
+
for (const f of earlyFailures)
|
|
249
|
+
record(f.path, { path: f.path, error: f.error });
|
|
250
|
+
for (const { value: r } of executed.results) {
|
|
251
|
+
if ('skipped' in r) {
|
|
252
|
+
record(r.path, { path: r.path, value: { deleted: false } });
|
|
253
|
+
}
|
|
254
|
+
else if ('failure' in r) {
|
|
255
|
+
record(r.failure.path, { path: r.failure.path, error: r.failure.error });
|
|
256
|
+
}
|
|
257
|
+
else if (r.item.path) {
|
|
258
|
+
record(r.item.path, { path: r.item.path, value: { deleted: true } });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const { index, error } of executed.errors) {
|
|
262
|
+
const plan = plans[index];
|
|
263
|
+
const path = plan?.validPath ?? '(unknown)';
|
|
264
|
+
record(path, {
|
|
265
|
+
path,
|
|
266
|
+
error: { code: ErrorCode.UNKNOWN, message: error.message },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// A plan's `validPath` is the resolved absolute path, so a requested relative
|
|
270
|
+
// path will not find itself in the map by name — fall back through the plan.
|
|
271
|
+
const planByRequested = new Map(plans.map((p) => [p.inputPath, p.validPath]));
|
|
272
|
+
const results = paths.map((requested) => {
|
|
273
|
+
const resolved = planByRequested.get(requested);
|
|
274
|
+
const entry = byPath.get(requested) ?? (resolved !== undefined ? byPath.get(resolved) : undefined);
|
|
275
|
+
return (entry ?? {
|
|
276
|
+
path: requested,
|
|
277
|
+
error: { code: ErrorCode.UNKNOWN, message: 'Unknown delete failure' },
|
|
278
|
+
});
|
|
100
279
|
});
|
|
280
|
+
const failed = results.filter((r) => r.error !== undefined).length;
|
|
281
|
+
return {
|
|
282
|
+
results,
|
|
283
|
+
summary: { total: results.length, succeeded: results.length - failed, failed },
|
|
284
|
+
};
|
|
101
285
|
}
|
|
286
|
+
export const DELETE_FILE = defineTool({
|
|
287
|
+
name: 'delete',
|
|
288
|
+
title: 'Delete File',
|
|
289
|
+
description: 'Permanently delete one or more files, directories, or symlinks (max 1000 per call). This action is irreversible. ' +
|
|
290
|
+
'Pass paths: [...] — there is no single-path form. ' +
|
|
291
|
+
'Non-empty directories require recursive=true and additionally prompt the user to confirm each one, ' +
|
|
292
|
+
'so the call returns without deleting anything until that confirmation comes back; ' +
|
|
293
|
+
'a client that cannot prompt gets an error naming the alternative. ' +
|
|
294
|
+
'Workspace root directories cannot be deleted.',
|
|
295
|
+
input: DeleteInputSchema,
|
|
296
|
+
output: DeleteOutputSchema,
|
|
297
|
+
// results[].value XOR results[].error, and `deleted: false` means the user
|
|
298
|
+
// chose Skip rather than a failure — not inferable from the description.
|
|
299
|
+
publishOutputSchema: true,
|
|
300
|
+
annotations: {
|
|
301
|
+
readOnlyHint: false,
|
|
302
|
+
idempotentHint: false,
|
|
303
|
+
destructiveHint: true,
|
|
304
|
+
openWorldHint: false,
|
|
305
|
+
},
|
|
306
|
+
defaultErrorCode: ErrorCode.UNKNOWN,
|
|
307
|
+
progress: (args) => ({
|
|
308
|
+
label: 'Delete',
|
|
309
|
+
subject: args.paths.length === 1
|
|
310
|
+
? basename(args.paths[0] ?? '')
|
|
311
|
+
: `${String(args.paths.length)} paths`,
|
|
312
|
+
}),
|
|
313
|
+
accessPaths: (args) => [...args.paths],
|
|
314
|
+
run: async (args, ctx) => {
|
|
315
|
+
const structured = await handleDelete(args, ctx);
|
|
316
|
+
// input_required is a return value, not a completed call: surface it
|
|
317
|
+
// verbatim so the executor short-circuits before building a CallToolResult.
|
|
318
|
+
if (isInputRequiredResult(structured))
|
|
319
|
+
return structured;
|
|
320
|
+
// Name every path and its outcome. The old text said "deleted 2 paths"
|
|
321
|
+
// without saying which two, so a partial failure was unreadable without
|
|
322
|
+
// parsing the structured half.
|
|
323
|
+
const { failed, total } = structured.summary;
|
|
324
|
+
// A lone failure carries its reason in the text. "delete: . FAILED (0/1 ok)"
|
|
325
|
+
// made the model re-read structuredContent to learn it was ACCESS_DENIED;
|
|
326
|
+
// with one path there is no ambiguity about which message applies.
|
|
327
|
+
if (total === 1 && failed === 1) {
|
|
328
|
+
const only = structured.results[0];
|
|
329
|
+
return {
|
|
330
|
+
structured,
|
|
331
|
+
text: `delete: ${only?.path ?? ''} FAILED — ${only?.error?.message ?? 'unknown error'}`,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const tokens = structured.results.map((r) => {
|
|
335
|
+
const label = pathLabel(r.path);
|
|
336
|
+
if (r.error)
|
|
337
|
+
return `${label} FAILED`;
|
|
338
|
+
return r.value?.deleted ? label : `${label} SKIPPED`;
|
|
339
|
+
});
|
|
340
|
+
const ratio = failed > 0 ? ` (${String(total - failed)}/${String(total)} ok)` : '';
|
|
341
|
+
return { structured, text: `delete: ${joinRoster(tokens)}${ratio}` };
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
//# sourceMappingURL=delete-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-file.js","sourceRoot":"","sources":["../../src/tools/delete-file.ts"],"names":[],"mappings":"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","sourcesContent":["import type { InputRequiredResult } from '@modelcontextprotocol/server';\nimport { isInputRequiredResult } from '@modelcontextprotocol/server';\n\nimport { basename } from 'node:path';\n\nimport * as z from 'zod/v4';\n\nimport { processInParallel } from '../core/concurrency.js';\nimport {\n ErrorCode,\n FsError,\n isFsError,\n isNodeError,\n isNotFoundErrno,\n Problem,\n} from '../core/errors.js';\nimport { joinRoster, pathLabel } from '../core/fmt.js';\nimport type { FileType, GuardedFileSystem } from '../core/fs.js';\nimport { getFileType } from '../core/fs.js';\nimport { choiceInput, pendingRoundTrip, readAcceptedChoice } from '../core/input-required.js';\nimport {\n defaultFalseBoolean,\n OperationSummarySchema,\n PerFileErrorSchema,\n RequiredPath,\n} from '../core/schema.js';\nimport { PARALLEL_CONCURRENCY } from '../core/util.js';\nimport type { ToolCtx } from './define.js';\nimport { defineTool } from './define.js';\n\nconst DeleteInputSchema = z.strictObject({\n paths: z\n .array(RequiredPath)\n .min(1)\n .max(1000)\n .describe('Paths to delete (max 1000); accepts files, directories, or symlinks'),\n recursive: defaultFalseBoolean(\n 'Delete directory contents recursively (required for non-empty directories)',\n ),\n ignoreIfNotExists: defaultFalseBoolean(\n 'Silently succeed if a path does not exist instead of returning an error',\n ),\n});\n\nconst DeletePerPathValueSchema = z.strictObject({\n deleted: z.boolean().describe('True when the path was removed; false when the user chose Skip'),\n});\n\nconst DeletePerPathSchema = z.strictObject({\n path: z.string().describe('Requested path'),\n value: DeletePerPathValueSchema.optional().describe('Delete outcome; present on success'),\n error: PerFileErrorSchema.optional().describe('Error details; present on failure'),\n});\n\n// One envelope for every batch tool: `read` and `stat` already answer with\n// `{ results, summary }`, so `delete` does too. The old shape switched between\n// `{ ok, path }` and `{ ok: false, failures }` depending on the outcome, which\n// left a caller parsing two schemas for one tool and never named the paths in a\n// multi-delete's text.\nconst DeleteOutputSchema = z.strictObject({\n results: z\n .array(DeletePerPathSchema)\n .describe('Per-path results ordered to match the input paths'),\n summary: OperationSummarySchema,\n});\n\ntype DeleteInput = z.infer<typeof DeleteInputSchema>;\ntype DeleteOutput = z.infer<typeof DeleteOutputSchema>;\ntype DeletePerPathResult = z.infer<typeof DeletePerPathSchema>;\n\n// Internal types for error handling\ninterface DeletedItem {\n path: string;\n}\ninterface DeleteFailure {\n path: string;\n error: {\n code: string;\n message: string;\n path?: string;\n suggestion?: string;\n };\n}\n\nconst ERR_NOT_EMPTY = new Error('Directory not empty. Set recursive: true.');\n\nfunction toDeleteFailure(path: string, error: unknown): DeleteFailure {\n // A missing path reached here as the raw errno text (\"ENOENT: no such file or\n // directory, lstat 'c:\\...'\"), while `stat` reported the same condition as\n // \"Path not found\". One condition, one message — and the syscall name stays\n // off the wire.\n if (isNotFoundErrno(error)) {\n return {\n path,\n error: Problem.toPerFileError(\n new FsError(ErrorCode.NOT_FOUND, 'Path not found', path),\n ErrorCode.NOT_FOUND,\n path,\n ),\n };\n }\n if (\n isNodeError(error) &&\n (error.code === 'ENOTEMPTY' || error.code === 'EISDIR' || error.code === 'EEXIST')\n ) {\n return {\n path,\n error: Problem.toPerFileError(ERR_NOT_EMPTY, ErrorCode.INVALID_INPUT, path),\n };\n }\n return { path, error: Problem.toPerFileError(error, ErrorCode.UNKNOWN, path) };\n}\n\ntype LstatResult = Awaited<ReturnType<GuardedFileSystem['lstat']>>;\ntype ItemType = FileType;\n\n/** A path's pre-checked plan: validated, statted, and flagged if it needs confirmation. */\ninterface DeletePlan {\n inputPath: string;\n validPath: string;\n itemType: ItemType;\n firstStats: LstatResult;\n /** Recursive + non-empty directory: needs a round-trip confirmation. */\n pending: boolean;\n}\n\ntype PlanResult =\n | { status: 'fail'; failure: DeleteFailure }\n | { status: 'noop'; item: DeletedItem }\n | { status: 'plan'; plan: DeletePlan };\n\n/**\n * Phase 1 (no mutation): validate, stat, and decide whether a path needs a\n * confirmation before anything is deleted. A pending path is a recursive\n * non-empty directory; everything else is ready to delete directly.\n */\nasync function planPath(\n inputPath: string,\n args: Pick<DeleteInput, 'recursive' | 'ignoreIfNotExists'>,\n fs: Pick<GuardedFileSystem, 'pathGuard' | 'lstat' | 'hasChildrenUnchecked'>,\n): Promise<PlanResult> {\n let validPath: string;\n try {\n validPath = await fs.pathGuard.validatePathForDelete(inputPath);\n } catch (error) {\n if (\n args.ignoreIfNotExists &&\n ((isFsError(error) && error.code === ErrorCode.NOT_FOUND) || isNotFoundErrno(error))\n ) {\n return { status: 'noop', item: { path: inputPath } };\n }\n return { status: 'fail', failure: toDeleteFailure(inputPath, error) };\n }\n\n if (fs.pathGuard.isAllowedRoot(validPath)) {\n return {\n status: 'fail',\n // Keyed by the REQUESTED path, like every other failure here: handleDelete\n // orders results by input path, and a resolved key would not be found for\n // a relative request.\n failure: {\n path: inputPath,\n error: Problem.accessDenied('Deleting a workspace root directory is not allowed', {\n path: validPath,\n }),\n },\n };\n }\n\n let firstStats: LstatResult;\n try {\n firstStats = await fs.lstat(validPath);\n } catch (error) {\n if (isNotFoundErrno(error) && args.ignoreIfNotExists) {\n return { status: 'noop', item: { path: inputPath } };\n }\n return { status: 'fail', failure: toDeleteFailure(inputPath, error) };\n }\n\n const itemType = getFileType(firstStats.stats);\n let hasChildren = false;\n if (args.recursive && itemType === 'directory') {\n try {\n hasChildren = await fs.hasChildrenUnchecked(validPath);\n } catch (error) {\n return { status: 'fail', failure: toDeleteFailure(inputPath, error) };\n }\n }\n const pending = hasChildren;\n return { status: 'plan', plan: { inputPath, validPath, itemType, firstStats, pending } };\n}\n\nasync function performDeletion(\n validPath: string,\n args: Pick<DeleteInput, 'recursive' | 'ignoreIfNotExists'>,\n isDirectory: boolean,\n fsOps: Pick<GuardedFileSystem, 'rm' | 'rmdir'>,\n): Promise<void> {\n if (isDirectory && !args.recursive) {\n await fsOps.rmdir(validPath);\n } else {\n await fsOps.rm(validPath, {\n recursive: args.recursive,\n force: args.ignoreIfNotExists,\n });\n }\n}\n\n/**\n * Phase 2 (mutation): TOCTOU re-stat against the plan's first stat, then\n * delete. A type or identity change across the confirmation gap fails closed\n * rather than deleting a replacement object the user never confirmed.\n */\nasync function finalizeDeletion(\n plan: DeletePlan,\n args: Pick<DeleteInput, 'recursive' | 'ignoreIfNotExists'>,\n ctx: Pick<ToolCtx, 'fs' | 'log'>,\n): Promise<{ item: DeletedItem } | { failure: DeleteFailure }> {\n let currentStats: LstatResult;\n try {\n currentStats = await ctx.fs.lstat(plan.validPath);\n } catch (error) {\n if (isNotFoundErrno(error) && args.ignoreIfNotExists) {\n return { item: { path: plan.validPath } };\n }\n return { failure: toDeleteFailure(plan.inputPath, error) };\n }\n\n const currentItemType = getFileType(currentStats.stats);\n // Identity comparison, not just the coarse category: a swap during the\n // confirmation gap (delete original, create a same-named replacement) keeps\n // the type but changes dev/ino/birthtimeMs. birthtimeMs is the primary\n // cross-platform signal — dev/ino can read 0 on some non-POSIX drivers — so\n // combine all three rather than trusting dev/ino alone.\n const identityChanged =\n plan.firstStats.stats.dev !== currentStats.stats.dev ||\n plan.firstStats.stats.ino !== currentStats.stats.ino ||\n plan.firstStats.stats.birthtimeMs !== currentStats.stats.birthtimeMs;\n if (plan.itemType !== 'other' && (currentItemType !== plan.itemType || identityChanged)) {\n return {\n failure: {\n path: plan.validPath,\n error: Problem.invalidInput(\n `Delete failed: item type changed from ${plan.itemType} to ${currentItemType} during confirmation.`,\n { path: plan.validPath },\n ),\n },\n };\n }\n\n try {\n await performDeletion(plan.validPath, args, currentStats.stats.isDirectory(), ctx.fs);\n } catch (error) {\n return { failure: toDeleteFailure(plan.inputPath, error) };\n }\n\n ctx.log?.('info', `rm: ${plan.inputPath}`, 'delete');\n return { item: { path: plan.validPath } };\n}\n\n/**\n * Execute one planned path on the retry round. A pending path deletes only on\n * an accepted `confirm: true`; decline, cancel, or a missing key report\n * `CANCELLED` for that path (R3 proceed, R4/R5 cancelled). A non-pending path\n * deletes directly (R14: non-pending items proceed alongside accepted ones).\n */\nasync function executePlan(\n plan: DeletePlan,\n args: Pick<DeleteInput, 'recursive' | 'ignoreIfNotExists'>,\n ctx: Pick<ToolCtx, 'fs' | 'inputResponses' | 'log'>,\n pendingSorted: readonly string[],\n): Promise<{ item: DeletedItem } | { failure: DeleteFailure } | { skipped: true; path: string }> {\n if (plan.pending) {\n const key = `confirm_${pendingSorted.indexOf(plan.validPath)}`;\n const choice = readAcceptedChoice(ctx.inputResponses, key);\n if (choice === 'skip') {\n return { skipped: true as const, path: plan.validPath };\n }\n if (choice !== 'delete') {\n return {\n failure: {\n path: plan.validPath,\n error: Problem.cancelled('Delete cancelled: confirmation was declined or missing', {\n path: plan.validPath,\n }),\n },\n };\n }\n }\n return finalizeDeletion(plan, args, ctx);\n}\n\nasync function handleDelete(\n args: DeleteInput,\n ctx: ToolCtx,\n): Promise<DeleteOutput | InputRequiredResult> {\n // Duplicate paths collapse: results are keyed by path, so a repeated entry\n // would emit two rows for one deletion and inflate `summary.total`.\n const paths = [...new Set(args.paths)];\n\n // Phase 1 (no mutation): plan every path.\n const planned = await processInParallel(\n paths,\n (inputPath) => planPath(inputPath, args, ctx.fs),\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n\n const plans: DeletePlan[] = [];\n const earlyFailures: DeleteFailure[] = [];\n const earlyNoop: DeletedItem[] = [];\n for (const { value: r } of planned.results) {\n if (r.status === 'fail') earlyFailures.push(r.failure);\n else if (r.status === 'noop') earlyNoop.push(r.item);\n else plans.push(r.plan);\n }\n for (const { index, error } of planned.errors) {\n const path = paths[index] ?? '(unknown)';\n earlyFailures.push({ path, error: { code: ErrorCode.UNKNOWN, message: error.message } });\n }\n\n // Pending set: recursive + non-empty directories, sorted and de-duplicated.\n // `buildInputRequired` seals exactly this set into the requestState, and the\n // retried round recomputes it from the same args so a swapped retry (accept\n // for X, retry with Y) is rejected — the codec only proves the state was not\n // tampered, not that it matches the current request (R9).\n const pendingSorted = [...new Set(plans.filter((p) => p.pending).map((p) => p.validPath))].sort();\n\n if (pendingSorted.length > 0) {\n // Round 1 returns input_required (atomic — R14: nothing deleted yet, not\n // even the non-pending items in the same call); a retry whose verified\n // state does not bind this pending set throws (R9) via `pendingRoundTrip`.\n const round = await pendingRoundTrip({\n op: 'delete',\n pending: pendingSorted,\n requestState: ctx.requestState,\n clientCapabilities: ctx.clientCapabilities,\n buildInputs: (ps) =>\n ps.map((p, i) =>\n choiceInput(\n `confirm_${i}`,\n `Permanently delete \"${p}\" and all its contents? This cannot be undone.`,\n [\n { value: 'delete', title: 'Delete' },\n { value: 'skip', title: 'Skip' },\n ],\n ),\n ),\n });\n if (round !== undefined) return round;\n }\n\n // Phase 2 (mutation): execute deletions for every planned path.\n const executed = await processInParallel(\n plans,\n (plan) => executePlan(plan, args, ctx, pendingSorted),\n PARALLEL_CONCURRENCY,\n ctx.signal,\n );\n\n // Collect by path first, then emit in the caller's input order so\n // `results[i]` lines up with `paths[i]` the way read/stat guarantee.\n const byPath = new Map<string, DeletePerPathResult>();\n const record = (path: string, entry: DeletePerPathResult): void => {\n byPath.set(path, entry);\n };\n for (const n of earlyNoop) record(n.path, { path: n.path, value: { deleted: true } });\n for (const f of earlyFailures) record(f.path, { path: f.path, error: f.error });\n for (const { value: r } of executed.results) {\n if ('skipped' in r) {\n record(r.path, { path: r.path, value: { deleted: false } });\n } else if ('failure' in r) {\n record(r.failure.path, { path: r.failure.path, error: r.failure.error });\n } else if (r.item.path) {\n record(r.item.path, { path: r.item.path, value: { deleted: true } });\n }\n }\n for (const { index, error } of executed.errors) {\n const plan = plans[index];\n const path = plan?.validPath ?? '(unknown)';\n record(path, {\n path,\n error: { code: ErrorCode.UNKNOWN, message: error.message },\n });\n }\n\n // A plan's `validPath` is the resolved absolute path, so a requested relative\n // path will not find itself in the map by name — fall back through the plan.\n const planByRequested = new Map(plans.map((p) => [p.inputPath, p.validPath]));\n const results: DeletePerPathResult[] = paths.map((requested) => {\n const resolved = planByRequested.get(requested);\n const entry =\n byPath.get(requested) ?? (resolved !== undefined ? byPath.get(resolved) : undefined);\n return (\n entry ?? {\n path: requested,\n error: { code: ErrorCode.UNKNOWN, message: 'Unknown delete failure' },\n }\n );\n });\n\n const failed = results.filter((r) => r.error !== undefined).length;\n return {\n results,\n summary: { total: results.length, succeeded: results.length - failed, failed },\n };\n}\n\nexport const DELETE_FILE = defineTool({\n name: 'delete',\n title: 'Delete File',\n description:\n 'Permanently delete one or more files, directories, or symlinks (max 1000 per call). This action is irreversible. ' +\n 'Pass paths: [...] — there is no single-path form. ' +\n 'Non-empty directories require recursive=true and additionally prompt the user to confirm each one, ' +\n 'so the call returns without deleting anything until that confirmation comes back; ' +\n 'a client that cannot prompt gets an error naming the alternative. ' +\n 'Workspace root directories cannot be deleted.',\n input: DeleteInputSchema,\n output: DeleteOutputSchema,\n // results[].value XOR results[].error, and `deleted: false` means the user\n // chose Skip rather than a failure — not inferable from the description.\n publishOutputSchema: true,\n annotations: {\n readOnlyHint: false,\n idempotentHint: false,\n destructiveHint: true,\n openWorldHint: false,\n },\n defaultErrorCode: ErrorCode.UNKNOWN,\n progress: (args) => ({\n label: 'Delete',\n subject:\n args.paths.length === 1\n ? basename(args.paths[0] ?? '')\n : `${String(args.paths.length)} paths`,\n }),\n accessPaths: (args) => [...args.paths],\n run: async (args, ctx) => {\n const structured = await handleDelete(args, ctx);\n // input_required is a return value, not a completed call: surface it\n // verbatim so the executor short-circuits before building a CallToolResult.\n if (isInputRequiredResult(structured)) return structured;\n // Name every path and its outcome. The old text said \"deleted 2 paths\"\n // without saying which two, so a partial failure was unreadable without\n // parsing the structured half.\n const { failed, total } = structured.summary;\n // A lone failure carries its reason in the text. \"delete: . FAILED (0/1 ok)\"\n // made the model re-read structuredContent to learn it was ACCESS_DENIED;\n // with one path there is no ambiguity about which message applies.\n if (total === 1 && failed === 1) {\n const only = structured.results[0];\n return {\n structured,\n text: `delete: ${only?.path ?? ''} FAILED — ${only?.error?.message ?? 'unknown error'}`,\n };\n }\n const tokens = structured.results.map((r) => {\n const label = pathLabel(r.path);\n if (r.error) return `${label} FAILED`;\n return r.value?.deleted ? label : `${label} SKIPPED`;\n });\n const ratio = failed > 0 ? ` (${String(total - failed)}/${String(total)} ok)` : '';\n return { structured, text: `delete: ${joinRoster(tokens)}${ratio}` };\n },\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/tools/diff.ts"],"names":[],"mappings":"AAqGA,eAAO,MAAM,IAAI,mCAoBf,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import * as z from 'zod/v4';
|
|
3
|
+
import { createTwoFilesPatch, diffLines } from 'diff';
|
|
4
|
+
import { NonNegInt, PositiveInt, RequiredPath } from '../core/schema.js';
|
|
5
|
+
import { putJsonResource } from '../core/store.js';
|
|
6
|
+
import { defineTool } from './define.js';
|
|
7
|
+
const DiffInputSchema = z.strictObject({
|
|
8
|
+
a: RequiredPath.describe('First file to compare'),
|
|
9
|
+
b: RequiredPath.describe('Second file to compare'),
|
|
10
|
+
context: PositiveInt.max(50)
|
|
11
|
+
.default(3)
|
|
12
|
+
.describe('Number of context lines surrounding each change (default: 3)'),
|
|
13
|
+
});
|
|
14
|
+
const DiffOutputSchema = z.strictObject({
|
|
15
|
+
a: z.string().describe('Resolved absolute path of the first file'),
|
|
16
|
+
b: z.string().describe('Resolved absolute path of the second file'),
|
|
17
|
+
// The unified diff itself rides the text content block; `resourceUri` holds
|
|
18
|
+
// the full copy for a client that wants to fetch it separately.
|
|
19
|
+
linesAdded: NonNegInt.describe('Number of lines added'),
|
|
20
|
+
linesRemoved: NonNegInt.describe('Number of lines removed'),
|
|
21
|
+
resourceUri: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('URI to the full diff in the resource store (present when resource store is enabled)'),
|
|
25
|
+
});
|
|
26
|
+
async function handleDiff(args, ctx) {
|
|
27
|
+
const [{ validPath: validA, content: contentA }, { validPath: validB, content: contentB }] = await Promise.all([
|
|
28
|
+
ctx.fs.readEditableText(args.a, { signal: ctx.signal, tool: 'diff' }),
|
|
29
|
+
ctx.fs.readEditableText(args.b, { signal: ctx.signal, tool: 'diff' }),
|
|
30
|
+
]);
|
|
31
|
+
// createTwoFilesPatch returns the unified diff string synchronously on diff v9
|
|
32
|
+
// (the { callback } option does not fire on this version — verified).
|
|
33
|
+
const diffText = createTwoFilesPatch(basename(validA), basename(validB), contentA, contentB, 'a', 'b', { context: args.context });
|
|
34
|
+
// diffLines returns the change list synchronously; count added/removed lines.
|
|
35
|
+
let linesAdded = 0;
|
|
36
|
+
let linesRemoved = 0;
|
|
37
|
+
for (const part of diffLines(contentA, contentB)) {
|
|
38
|
+
if (part.added)
|
|
39
|
+
linesAdded += part.count;
|
|
40
|
+
else if (part.removed)
|
|
41
|
+
linesRemoved += part.count;
|
|
42
|
+
}
|
|
43
|
+
let resourceUri;
|
|
44
|
+
let link;
|
|
45
|
+
// Identical files: the diff has no hunks, so externalizing it buys the client
|
|
46
|
+
// a link to nothing and burns a store slot the next real diff wants.
|
|
47
|
+
if (ctx.resourceStore && linesAdded + linesRemoved > 0) {
|
|
48
|
+
const result = putJsonResource(ctx.resourceStore, 'diff', {
|
|
49
|
+
diff: diffText,
|
|
50
|
+
linesAdded,
|
|
51
|
+
linesRemoved,
|
|
52
|
+
a: validA,
|
|
53
|
+
b: validB,
|
|
54
|
+
});
|
|
55
|
+
resourceUri = result.entry.uri;
|
|
56
|
+
link = result.link;
|
|
57
|
+
}
|
|
58
|
+
// The diff shipped three times: this text block, `structured.diff`, and the
|
|
59
|
+
// externalized store entry. The text block is the one a model reads, so the
|
|
60
|
+
// structured copy goes — `resourceUri` still holds the full diff for a client
|
|
61
|
+
// that wants to fetch it separately.
|
|
62
|
+
return {
|
|
63
|
+
structured: {
|
|
64
|
+
a: validA,
|
|
65
|
+
b: validB,
|
|
66
|
+
linesAdded,
|
|
67
|
+
linesRemoved,
|
|
68
|
+
...(resourceUri !== undefined ? { resourceUri } : {}),
|
|
69
|
+
},
|
|
70
|
+
text: diffText,
|
|
71
|
+
...(link !== undefined ? { resources: [link] } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export const DIFF = defineTool({
|
|
75
|
+
name: 'diff',
|
|
76
|
+
title: 'Diff',
|
|
77
|
+
description: 'Compare two files and return a unified diff with line counts. Pass the two paths as a and b. ' +
|
|
78
|
+
'Use after an edit dry-run to compare against another file, or to inspect changes between two paths.',
|
|
79
|
+
input: DiffInputSchema,
|
|
80
|
+
output: DiffOutputSchema,
|
|
81
|
+
annotations: {
|
|
82
|
+
readOnlyHint: true,
|
|
83
|
+
idempotentHint: true,
|
|
84
|
+
destructiveHint: false,
|
|
85
|
+
openWorldHint: false,
|
|
86
|
+
},
|
|
87
|
+
progress: (args) => ({
|
|
88
|
+
label: 'Diff',
|
|
89
|
+
subject: basename(args.a),
|
|
90
|
+
}),
|
|
91
|
+
accessPaths: (args) => [args.a, args.b],
|
|
92
|
+
run: (args, ctx) => handleDiff(args, ctx),
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/tools/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContentBlock } from '@modelcontextprotocol/server';\n\nimport { basename } from 'node:path';\n\nimport * as z from 'zod/v4';\nimport { createTwoFilesPatch, diffLines } from 'diff';\n\nimport { NonNegInt, PositiveInt, RequiredPath } from '../core/schema.js';\nimport { putJsonResource } from '../core/store.js';\nimport { defineTool, type ToolCtx } from './define.js';\n\nconst DiffInputSchema = z.strictObject({\n a: RequiredPath.describe('First file to compare'),\n b: RequiredPath.describe('Second file to compare'),\n context: PositiveInt.max(50)\n .default(3)\n .describe('Number of context lines surrounding each change (default: 3)'),\n});\n\nconst DiffOutputSchema = z.strictObject({\n a: z.string().describe('Resolved absolute path of the first file'),\n b: z.string().describe('Resolved absolute path of the second file'),\n // The unified diff itself rides the text content block; `resourceUri` holds\n // the full copy for a client that wants to fetch it separately.\n linesAdded: NonNegInt.describe('Number of lines added'),\n linesRemoved: NonNegInt.describe('Number of lines removed'),\n resourceUri: z\n .string()\n .optional()\n .describe(\n 'URI to the full diff in the resource store (present when resource store is enabled)',\n ),\n});\n\nasync function handleDiff(\n args: z.infer<typeof DiffInputSchema>,\n ctx: ToolCtx,\n): Promise<{\n structured: z.infer<typeof DiffOutputSchema>;\n text: string;\n resources?: ContentBlock[];\n}> {\n const [{ validPath: validA, content: contentA }, { validPath: validB, content: contentB }] =\n await Promise.all([\n ctx.fs.readEditableText(args.a, { signal: ctx.signal, tool: 'diff' }),\n ctx.fs.readEditableText(args.b, { signal: ctx.signal, tool: 'diff' }),\n ]);\n\n // createTwoFilesPatch returns the unified diff string synchronously on diff v9\n // (the { callback } option does not fire on this version — verified).\n const diffText = createTwoFilesPatch(\n basename(validA),\n basename(validB),\n contentA,\n contentB,\n 'a',\n 'b',\n { context: args.context },\n );\n\n // diffLines returns the change list synchronously; count added/removed lines.\n let linesAdded = 0;\n let linesRemoved = 0;\n for (const part of diffLines(contentA, contentB)) {\n if (part.added) linesAdded += part.count;\n else if (part.removed) linesRemoved += part.count;\n }\n\n let resourceUri: string | undefined;\n let link: ReturnType<typeof putJsonResource>['link'] | undefined;\n // Identical files: the diff has no hunks, so externalizing it buys the client\n // a link to nothing and burns a store slot the next real diff wants.\n if (ctx.resourceStore && linesAdded + linesRemoved > 0) {\n const result = putJsonResource(ctx.resourceStore, 'diff', {\n diff: diffText,\n linesAdded,\n linesRemoved,\n a: validA,\n b: validB,\n });\n resourceUri = result.entry.uri;\n link = result.link;\n }\n\n // The diff shipped three times: this text block, `structured.diff`, and the\n // externalized store entry. The text block is the one a model reads, so the\n // structured copy goes — `resourceUri` still holds the full diff for a client\n // that wants to fetch it separately.\n return {\n structured: {\n a: validA,\n b: validB,\n linesAdded,\n linesRemoved,\n ...(resourceUri !== undefined ? { resourceUri } : {}),\n },\n text: diffText,\n ...(link !== undefined ? { resources: [link] } : {}),\n };\n}\n\nexport const DIFF = defineTool({\n name: 'diff',\n title: 'Diff',\n description:\n 'Compare two files and return a unified diff with line counts. Pass the two paths as a and b. ' +\n 'Use after an edit dry-run to compare against another file, or to inspect changes between two paths.',\n input: DiffInputSchema,\n output: DiffOutputSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: false,\n },\n progress: (args) => ({\n label: 'Diff',\n subject: basename(args.a),\n }),\n accessPaths: (args) => [args.a, args.b],\n run: (args, ctx) => handleDiff(args, ctx),\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"AAogBA,eAAO,MAAM,IAAI,mCAyEf,CAAC"}
|