@j0hanz/filesystem-mcp 1.13.2 → 1.14.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 +162 -145
- package/dist/cli.js +2 -2
- package/dist/completions.js +54 -51
- package/dist/config.d.ts +13 -14
- package/dist/config.js +12 -12
- package/dist/index.js +1 -1
- package/dist/lib/abort.d.ts +7 -0
- package/dist/lib/abort.js +81 -0
- package/dist/lib/constants.d.ts +3 -1
- package/dist/lib/constants.js +8 -2
- package/dist/lib/errors.d.ts +7 -3
- package/dist/lib/errors.js +64 -41
- package/dist/lib/file-operations/core.d.ts +3 -3
- package/dist/lib/file-operations/core.js +23 -20
- package/dist/lib/file-operations/metadata.d.ts +2 -2
- package/dist/lib/file-operations/metadata.js +69 -22
- package/dist/lib/file-operations/search.d.ts +0 -1
- package/dist/lib/file-operations/search.js +87 -95
- package/dist/lib/file-operations/traversal.js +13 -15
- package/dist/lib/fs-helpers.d.ts +3 -10
- package/dist/lib/fs-helpers.js +29 -108
- package/dist/lib/globs.d.ts +2 -0
- package/dist/lib/globs.js +19 -0
- package/dist/lib/logger.d.ts +28 -0
- package/dist/lib/logger.js +91 -0
- package/dist/lib/observability.d.ts +7 -0
- package/dist/lib/observability.js +19 -9
- package/dist/lib/paths.js +55 -55
- package/dist/lib/resource-store.js +4 -4
- package/dist/lib/utils.d.ts +0 -12
- package/dist/lib/utils.js +0 -13
- package/dist/lib/zod-codecs.d.ts +2 -0
- package/dist/lib/zod-codecs.js +18 -0
- package/dist/pkg-info.d.ts +1 -0
- package/dist/pkg-info.js +2 -2
- package/dist/prompts.js +3 -3
- package/dist/resources/generated-instructions.js +41 -41
- package/dist/resources/tool-catalog.js +33 -58
- package/dist/resources/tool-info.d.ts +0 -1
- package/dist/resources/tool-info.js +44 -67
- package/dist/resources/workflows.js +47 -19
- package/dist/resources.d.ts +1 -1
- package/dist/resources.js +4 -4
- package/dist/schemas.d.ts +185 -465
- package/dist/schemas.js +174 -206
- package/dist/server/bootstrap.d.ts +12 -11
- package/dist/server/bootstrap.js +95 -86
- package/dist/server/roots-manager.d.ts +5 -2
- package/dist/server/roots-manager.js +9 -7
- package/dist/server/task-store.d.ts +10 -0
- package/dist/server/task-store.js +73 -0
- package/dist/tools/apply-patch.js +39 -20
- package/dist/tools/calculate-hash.js +14 -27
- package/dist/tools/create-directory.js +11 -9
- package/dist/tools/delete-file.js +19 -19
- package/dist/tools/diff-files.js +16 -18
- package/dist/tools/edit-file.js +11 -5
- package/dist/tools/list-directory.js +16 -21
- package/dist/tools/move-file.js +105 -100
- package/dist/tools/read-multiple.js +15 -10
- package/dist/tools/read.js +6 -7
- package/dist/tools/replace-in-files.js +76 -115
- package/dist/tools/roots.js +3 -7
- package/dist/tools/search-content.js +158 -203
- package/dist/tools/search-files.js +59 -50
- package/dist/tools/shared.d.ts +10 -0
- package/dist/tools/shared.js +105 -36
- package/dist/tools/stat-many.js +15 -9
- package/dist/tools/stat.js +6 -6
- package/dist/tools/task-support.d.ts +10 -9
- package/dist/tools/task-support.js +94 -23
- package/dist/tools/tree.js +4 -4
- package/dist/tools/write-file.js +11 -12
- package/package.json +10 -9
package/dist/tools/move-file.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { cp, mkdir, rename, rm, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
3
|
+
import { withAbort } from '../lib/abort.js';
|
|
3
4
|
import { ErrorCode, formatUnknownErrorMessage, isNodeError, McpError, } from '../lib/errors.js';
|
|
4
|
-
import {
|
|
5
|
+
import { Logger } from '../lib/logger.js';
|
|
5
6
|
import { assertAllowedFileAccess, validateExistingPath, validatePathForWrite, } from '../lib/paths.js';
|
|
6
7
|
import { MoveFileInputSchema, MoveFileOutputSchema } from '../schemas.js';
|
|
7
|
-
import { buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
|
+
import { buildStructuredError, buildToolErrorResponse, buildToolResponse, DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS, executeToolWithDiagnostics, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
9
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
10
|
export const MOVE_FILE_TOOL = {
|
|
10
11
|
name: 'mv',
|
|
@@ -13,121 +14,124 @@ export const MOVE_FILE_TOOL = {
|
|
|
13
14
|
inputSchema: MoveFileInputSchema,
|
|
14
15
|
outputSchema: MoveFileOutputSchema,
|
|
15
16
|
annotations: DESTRUCTIVE_WRITE_TOOL_ANNOTATIONS,
|
|
16
|
-
nuances: ['Cross-device moves fall back to copy+delete.'],
|
|
17
17
|
gotchas: [
|
|
18
18
|
'On POSIX, an existing destination is silently overwritten; on Windows, rename fails with EEXIST if destination exists.',
|
|
19
19
|
],
|
|
20
20
|
taskSupport: 'forbidden',
|
|
21
21
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
function toMoveFailure(source, error, defaultCode = ErrorCode.UNKNOWN) {
|
|
23
|
+
return {
|
|
24
|
+
source,
|
|
25
|
+
error: buildStructuredError(error, defaultCode, source),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async function handleMoveError(error, src, validSource, targetPath, movedSources, failed, signal) {
|
|
29
|
+
if (isNodeError(error) && error.code === 'EXDEV') {
|
|
30
|
+
try {
|
|
31
|
+
await withAbort(cp(validSource, targetPath, { recursive: true }), signal);
|
|
32
|
+
}
|
|
33
|
+
catch (copyError) {
|
|
34
|
+
failed.push(toMoveFailure(src, copyError));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
await withAbort(rm(validSource, { recursive: true, force: true }), signal);
|
|
39
|
+
movedSources.push(validSource);
|
|
40
|
+
}
|
|
41
|
+
catch (deleteError) {
|
|
42
|
+
try {
|
|
43
|
+
await rm(targetPath, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
failed.push(toMoveFailure(src, new McpError(ErrorCode.UNKNOWN, `Cross-device move partial: data at both source and destination. ${formatUnknownErrorMessage(deleteError)}`, src)));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
failed.push(toMoveFailure(src, new McpError(ErrorCode.UNKNOWN, `Cross-device move failed: source not removed. ${formatUnknownErrorMessage(deleteError)}`, src)));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
failed.push(toMoveFailure(src, error));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function processSingleMove(src, destIsDirectory, validDest, movedSources, failed, signal) {
|
|
57
|
+
let validSource;
|
|
58
|
+
try {
|
|
59
|
+
validSource = await validateExistingPath(src, signal);
|
|
60
|
+
assertAllowedFileAccess(src, validSource);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
failed.push(toMoveFailure(src, error, ErrorCode.ACCESS_DENIED));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const targetPath = destIsDirectory
|
|
67
|
+
? join(validDest, basename(validSource))
|
|
68
|
+
: validDest;
|
|
69
|
+
if (resolve(validSource) === resolve(targetPath)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (resolve(targetPath).startsWith(resolve(validSource) + sep)) {
|
|
73
|
+
failed.push(toMoveFailure(src, new McpError(ErrorCode.INVALID_INPUT, 'Cannot move a directory into its own subdirectory', src), ErrorCode.INVALID_INPUT));
|
|
74
|
+
return;
|
|
26
75
|
}
|
|
27
|
-
const validDest = await validatePathForWrite(args.destination, signal);
|
|
28
|
-
// Check if destination exists and is a directory
|
|
29
|
-
let destIsDirectory = false;
|
|
30
76
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
77
|
+
await withAbort(rename(validSource, targetPath), signal);
|
|
78
|
+
movedSources.push(validSource);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
await handleMoveError(error, src, validSource, targetPath, movedSources, failed, signal);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function getDestinationStatus(validDest) {
|
|
85
|
+
try {
|
|
86
|
+
const stats = await stat(validDest);
|
|
87
|
+
return stats.isDirectory();
|
|
33
88
|
}
|
|
34
89
|
catch (error) {
|
|
35
90
|
if (isNodeError(error) && error.code !== 'ENOENT') {
|
|
36
91
|
throw error;
|
|
37
92
|
}
|
|
38
93
|
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
function formatMoveMessage(moved, failed, destination) {
|
|
97
|
+
const movedItemStr = `item${moved === 1 ? '' : 's'}`;
|
|
98
|
+
const failedItemStr = `item${failed === 1 ? '' : 's'}`;
|
|
99
|
+
if (failed > 0) {
|
|
100
|
+
return `Moved ${moved} ${movedItemStr}; failed to move ${failed} ${failedItemStr}`;
|
|
101
|
+
}
|
|
102
|
+
return `Successfully moved ${moved} ${movedItemStr} to ${destination}`;
|
|
103
|
+
}
|
|
104
|
+
async function handleMoveFile(args, signal) {
|
|
105
|
+
const sources = args.sources ?? (args.source ? [args.source] : []);
|
|
106
|
+
if (sources.length === 0) {
|
|
107
|
+
throw new McpError(ErrorCode.INVALID_INPUT, 'No sources provided.');
|
|
108
|
+
}
|
|
109
|
+
const validDest = await validatePathForWrite(args.destination, signal);
|
|
110
|
+
const destIsDirectory = await getDestinationStatus(validDest);
|
|
39
111
|
if (sources.length > 1 && !destIsDirectory) {
|
|
40
|
-
throw new McpError(ErrorCode.
|
|
112
|
+
throw new McpError(ErrorCode.INVALID_INPUT, 'Destination must be an existing directory for multiple sources.');
|
|
41
113
|
}
|
|
42
|
-
// Ensure destination parent directory exists if it's not an existing directory
|
|
43
114
|
if (!destIsDirectory) {
|
|
44
|
-
await withAbort(
|
|
115
|
+
await withAbort(mkdir(dirname(validDest), { recursive: true }), signal);
|
|
45
116
|
}
|
|
46
117
|
const movedSources = [];
|
|
47
118
|
const failed = [];
|
|
48
119
|
for (const src of sources) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
const targetPath = destIsDirectory
|
|
62
|
-
? path.join(validDest, path.basename(validSource))
|
|
63
|
-
: validDest;
|
|
64
|
-
// Prevent moving a file onto itself
|
|
65
|
-
if (path.resolve(validSource) === path.resolve(targetPath)) {
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
// Prevent moving a directory into its own subdirectory
|
|
69
|
-
// Fixes "Missing validation for moving directory into its own subdirectory" finding
|
|
70
|
-
if (path.resolve(targetPath).startsWith(path.resolve(validSource) + path.sep)) {
|
|
71
|
-
failed.push({
|
|
72
|
-
source: src,
|
|
73
|
-
error: `Cannot move directory '${src}' into its own subdirectory '${targetPath}'`,
|
|
74
|
-
});
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
try {
|
|
78
|
-
await withAbort(fs.rename(validSource, targetPath), signal);
|
|
79
|
-
movedSources.push(validSource);
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
if (isNodeError(error) && error.code === 'EXDEV') {
|
|
83
|
-
// Cross-device link, fallback to copy + delete
|
|
84
|
-
try {
|
|
85
|
-
await withAbort(fs.cp(validSource, targetPath, { recursive: true }), signal);
|
|
86
|
-
}
|
|
87
|
-
catch (copyError) {
|
|
88
|
-
failed.push({
|
|
89
|
-
source: src,
|
|
90
|
-
error: formatUnknownErrorMessage(copyError),
|
|
91
|
-
});
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
// Copy succeeded — now remove source
|
|
95
|
-
try {
|
|
96
|
-
await withAbort(fs.rm(validSource, { recursive: true, force: true }), signal);
|
|
97
|
-
movedSources.push(validSource);
|
|
98
|
-
}
|
|
99
|
-
catch (deleteError) {
|
|
100
|
-
// Source delete failed after copy — rollback by removing the copy
|
|
101
|
-
try {
|
|
102
|
-
await fs.rm(targetPath, { recursive: true, force: true });
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// Rollback failed — data exists in both locations
|
|
106
|
-
failed.push({
|
|
107
|
-
source: src,
|
|
108
|
-
error: `Cross-device move partially failed: data exists at both '${validSource}' and '${targetPath}'. ${formatUnknownErrorMessage(deleteError)}`,
|
|
109
|
-
});
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
failed.push({
|
|
113
|
-
source: src,
|
|
114
|
-
error: `Cross-device move failed: could not remove source. ${formatUnknownErrorMessage(deleteError)}`,
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
failed.push({
|
|
120
|
-
source: src,
|
|
121
|
-
error: formatUnknownErrorMessage(error),
|
|
122
|
-
});
|
|
123
|
-
}
|
|
120
|
+
await processSingleMove(src, destIsDirectory, validDest, movedSources, failed, signal);
|
|
121
|
+
}
|
|
122
|
+
const message = formatMoveMessage(movedSources.length, failed.length, args.destination);
|
|
123
|
+
const movedSource = sources.length === 1 ? movedSources[0] : undefined;
|
|
124
|
+
const failedSuffix = failed.length > 0 ? ` (${failed.length} failed)` : '';
|
|
125
|
+
Logger.info(`mv: ${movedSources.length} item(s) → ${args.destination}${failedSuffix}`);
|
|
126
|
+
if (movedSources.length === 0 && failed.length > 0) {
|
|
127
|
+
const firstFailure = failed[0];
|
|
128
|
+
if (firstFailure) {
|
|
129
|
+
throw new McpError(firstFailure.error.code, message, firstFailure.error.path);
|
|
124
130
|
}
|
|
125
131
|
}
|
|
126
|
-
const message = failed.length > 0
|
|
127
|
-
? `Moved ${movedSources.length} item${movedSources.length === 1 ? '' : 's'}; failed to move ${failed.length} item${failed.length === 1 ? '' : 's'}`
|
|
128
|
-
: `Successfully moved ${movedSources.length} item${movedSources.length === 1 ? '' : 's'} to ${args.destination}`;
|
|
129
132
|
return buildToolResponse(message, {
|
|
130
133
|
ok: failed.length === 0,
|
|
134
|
+
...(movedSource ? { source: movedSource } : {}),
|
|
131
135
|
sources: movedSources,
|
|
132
136
|
destination: validDest,
|
|
133
137
|
...(failed.length > 0 ? { failed } : {}),
|
|
@@ -137,25 +141,26 @@ export function registerMoveFileTool(server, options = {}) {
|
|
|
137
141
|
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
138
142
|
toolName: 'mv',
|
|
139
143
|
extra,
|
|
144
|
+
outputSchema: MoveFileOutputSchema,
|
|
140
145
|
timedSignal: {},
|
|
141
146
|
context: { path: args.source ?? args.sources?.[0] },
|
|
142
147
|
run: (signal) => handleMoveFile(args, signal),
|
|
143
|
-
onError: (error) => buildToolErrorResponse(error, ErrorCode.
|
|
148
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.UNKNOWN, args.source ?? args.sources?.[0]),
|
|
144
149
|
});
|
|
145
150
|
const wrappedHandler = wrapToolHandler(handler, {
|
|
146
151
|
guard: options.isInitialized,
|
|
147
152
|
progressMessage: (args) => {
|
|
148
|
-
const dest =
|
|
153
|
+
const dest = basename(args.destination);
|
|
149
154
|
if (args.source && !args.sources?.length) {
|
|
150
|
-
return `🛠 mv: ${
|
|
155
|
+
return `🛠 mv: ${basename(args.source)} → ${dest}`;
|
|
151
156
|
}
|
|
152
157
|
const count = (args.source ? 1 : 0) + (args.sources?.length ?? 0);
|
|
153
158
|
return `🛠 mv: ${count} items → ${dest}`;
|
|
154
159
|
},
|
|
155
160
|
completionMessage: (args, result) => {
|
|
156
|
-
const dest =
|
|
161
|
+
const dest = basename(args.destination);
|
|
157
162
|
if (args.source && !args.sources?.length) {
|
|
158
|
-
const src =
|
|
163
|
+
const src = basename(args.source);
|
|
159
164
|
if (result.isError)
|
|
160
165
|
return `🛠 mv: ${src} → ${dest} • failed`;
|
|
161
166
|
return `🛠 mv: ${src} → ${dest}`;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
2
|
import { DEFAULT_SEARCH_TIMEOUT_MS } from '../lib/constants.js';
|
|
3
3
|
import { ErrorCode } from '../lib/errors.js';
|
|
4
4
|
import { readMultipleFiles } from '../lib/file-operations/metadata.js';
|
|
5
5
|
import { ReadMultipleFilesInputSchema, ReadMultipleFilesOutputSchema, } from '../schemas.js';
|
|
6
|
-
import { buildBatchCompletionSuffix, buildBatchPathContext, buildResourceLink, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
6
|
+
import { buildBatchCompletionSuffix, buildBatchPathContext, buildResourceLink, buildStructuredError, buildToolErrorResponse, buildToolResponse, createBatchProgressCallbacks, executeToolWithDiagnostics, maybeExternalizeTextContent, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
7
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
8
8
|
const READ_MANY_TOOL_NAME = 'read_many';
|
|
9
9
|
const READ_MANY_TOOL_LABEL = '🕮 read_many';
|
|
@@ -17,13 +17,9 @@ export const READ_MANY_TOOL = {
|
|
|
17
17
|
outputSchema: ReadMultipleFilesOutputSchema,
|
|
18
18
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
19
19
|
taskSupport: 'optional',
|
|
20
|
-
nuances: ['Total read budget is capped by `MAX_READ_MANY_TOTAL_SIZE`.'],
|
|
21
|
-
gotchas: [
|
|
22
|
-
'Per-file `truncationReason` can be `head`, `tail`, `range`, or `externalized`.',
|
|
23
|
-
],
|
|
24
20
|
};
|
|
25
21
|
function buildReadManyResourceName(filePath) {
|
|
26
|
-
return `read:${
|
|
22
|
+
return `read:${basename(filePath)}`;
|
|
27
23
|
}
|
|
28
24
|
function toStructuredReadManyResult(result) {
|
|
29
25
|
const structuredResult = {
|
|
@@ -72,8 +68,12 @@ function resolveReadManyTruncationReason(result) {
|
|
|
72
68
|
}
|
|
73
69
|
function maybeExternalizeReadManyResult(result, resourceStore) {
|
|
74
70
|
const truncationReason = resolveReadManyTruncationReason(result);
|
|
71
|
+
const { error, ...rest } = result;
|
|
75
72
|
const baseResult = {
|
|
76
|
-
...
|
|
73
|
+
...rest,
|
|
74
|
+
...(error
|
|
75
|
+
? { error: buildStructuredError(error, ErrorCode.UNKNOWN, result.path) }
|
|
76
|
+
: {}),
|
|
77
77
|
...(truncationReason ? { truncationReason } : {}),
|
|
78
78
|
};
|
|
79
79
|
if (!result.content) {
|
|
@@ -89,12 +89,13 @@ function maybeExternalizeReadManyResult(result, resourceStore) {
|
|
|
89
89
|
truncated: true,
|
|
90
90
|
resourceUri: externalized.entry.uri,
|
|
91
91
|
truncationReason: 'externalized',
|
|
92
|
+
expiresAt: externalized.entry.expiresAt,
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
95
|
function buildReadManyTextSection(result) {
|
|
95
96
|
const header = `=== ${result.path} ===`;
|
|
96
97
|
if (result.error) {
|
|
97
|
-
return `${header}\nError: ${result.error}`;
|
|
98
|
+
return `${header}\nError [${result.error.code}]: ${result.error.message}`;
|
|
98
99
|
}
|
|
99
100
|
return `${header}\n${result.content ?? ''}`;
|
|
100
101
|
}
|
|
@@ -128,6 +129,9 @@ function buildReadManyResponsePayload(results, resourceStore) {
|
|
|
128
129
|
uri: mappedResult.resourceUri,
|
|
129
130
|
name: buildReadManyResourceName(mappedResult.path),
|
|
130
131
|
description: FULL_FILE_CONTENTS_DESCRIPTION,
|
|
132
|
+
...(mappedResult.expiresAt
|
|
133
|
+
? { expiresAt: mappedResult.expiresAt }
|
|
134
|
+
: {}),
|
|
131
135
|
}));
|
|
132
136
|
}
|
|
133
137
|
if (mappedResult.error === undefined)
|
|
@@ -162,6 +166,7 @@ export function registerReadMultipleFilesTool(server, options = {}) {
|
|
|
162
166
|
return executeToolWithDiagnostics({
|
|
163
167
|
toolName: READ_MANY_TOOL_NAME,
|
|
164
168
|
extra,
|
|
169
|
+
outputSchema: ReadMultipleFilesOutputSchema,
|
|
165
170
|
timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
|
|
166
171
|
context: { path: primaryPath },
|
|
167
172
|
run: async (signal) => {
|
|
@@ -186,7 +191,7 @@ export function registerReadMultipleFilesTool(server, options = {}) {
|
|
|
186
191
|
throw error;
|
|
187
192
|
}
|
|
188
193
|
},
|
|
189
|
-
onError: (error) => buildToolErrorResponse(error, ErrorCode.
|
|
194
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FILE, primaryPath),
|
|
190
195
|
});
|
|
191
196
|
};
|
|
192
197
|
const wrappedHandler = wrapToolHandler(handler, {
|
package/dist/tools/read.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as path from 'node:path';
|
|
2
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
3
|
import { DEFAULT_SEARCH_TIMEOUT_MS, MAX_TEXT_FILE_SIZE, } from '../lib/constants.js';
|
|
4
4
|
import { ErrorCode } from '../lib/errors.js';
|
|
5
5
|
import { readFile } from '../lib/fs-helpers.js';
|
|
@@ -24,7 +24,7 @@ const READ_TOOL_NAME = 'read';
|
|
|
24
24
|
const READ_TOOL_LABEL = '🕮 read';
|
|
25
25
|
const FULL_FILE_CONTENTS_DESCRIPTION = 'Full file contents';
|
|
26
26
|
function buildReadResourceName(filePath) {
|
|
27
|
-
return `read:${
|
|
27
|
+
return `read:${basename(filePath)}`;
|
|
28
28
|
}
|
|
29
29
|
function buildReadOptions(args, signal) {
|
|
30
30
|
const options = {
|
|
@@ -99,7 +99,7 @@ function maybeBuildExternalizedReadResponse(filePath, content, structured, resou
|
|
|
99
99
|
]);
|
|
100
100
|
}
|
|
101
101
|
function buildReadProgressMessage(args) {
|
|
102
|
-
const name =
|
|
102
|
+
const name = basename(args.path);
|
|
103
103
|
if (args.startLine !== undefined) {
|
|
104
104
|
const end = args.endLine ?? '…';
|
|
105
105
|
return `${READ_TOOL_LABEL}: ${name} [lines ${args.startLine}–${end}]`;
|
|
@@ -111,12 +111,10 @@ function buildReadProgressMessage(args) {
|
|
|
111
111
|
return `${READ_TOOL_LABEL}: ${name}`;
|
|
112
112
|
}
|
|
113
113
|
function buildReadCompletionMessage(args, result) {
|
|
114
|
-
const name =
|
|
114
|
+
const name = basename(args.path);
|
|
115
115
|
if (result.isError)
|
|
116
116
|
return `${READ_TOOL_LABEL}: ${name} • failed`;
|
|
117
117
|
const structured = result.structuredContent;
|
|
118
|
-
if (!structured.ok)
|
|
119
|
-
return `${READ_TOOL_LABEL}: ${name} • failed`;
|
|
120
118
|
const lines = structured.linesRead ?? structured.totalLines;
|
|
121
119
|
if (structured.startLine !== undefined) {
|
|
122
120
|
const end = structured.linesRead
|
|
@@ -158,10 +156,11 @@ export function registerReadFileTool(server, options = {}) {
|
|
|
158
156
|
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
159
157
|
toolName: READ_TOOL_NAME,
|
|
160
158
|
extra,
|
|
159
|
+
outputSchema: ReadFileOutputSchema,
|
|
161
160
|
timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
|
|
162
161
|
context: { path: args.path },
|
|
163
162
|
run: (signal) => handleReadFile(args, signal, options.resourceStore),
|
|
164
|
-
onError: (error) => buildToolErrorResponse(error, ErrorCode.
|
|
163
|
+
onError: (error) => buildToolErrorResponse(error, ErrorCode.NOT_FILE, args.path),
|
|
165
164
|
});
|
|
166
165
|
const wrappedHandler = wrapToolHandler(handler, {
|
|
167
166
|
guard: options.isInitialized,
|