@j0hanz/filesystem-mcp 1.13.1 → 1.14.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 +117 -100
- package/dist/completions.d.ts +0 -11
- package/dist/completions.js +1 -1
- package/dist/config.d.ts +0 -1
- package/dist/lib/constants.d.ts +0 -1
- package/dist/lib/constants.js +0 -1
- package/dist/lib/errors.d.ts +0 -1
- package/dist/lib/errors.js +5 -9
- package/dist/lib/file-operations/core.d.ts +3 -2
- package/dist/lib/file-operations/metadata.js +9 -3
- package/dist/lib/file-operations/search.d.ts +1 -59
- package/dist/lib/file-operations/search.js +9 -22
- package/dist/lib/file-operations/traversal.d.ts +2 -2
- package/dist/lib/fs-helpers.d.ts +1 -2
- package/dist/lib/fs-helpers.js +10 -12
- package/dist/lib/globs.d.ts +2 -0
- package/dist/lib/globs.js +19 -0
- package/dist/lib/paths.d.ts +0 -3
- package/dist/lib/paths.js +1 -4
- package/dist/lib/utils.d.ts +4 -4
- package/dist/lib/utils.js +1 -10
- 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 +3 -12
- package/dist/resources/tool-catalog.js +10 -41
- package/dist/resources/tool-info.d.ts +0 -1
- package/dist/resources/tool-info.js +24 -39
- package/dist/resources/workflows.js +8 -1
- package/dist/schemas.d.ts +179 -481
- package/dist/schemas.js +156 -169
- package/dist/server/bootstrap.d.ts +0 -10
- package/dist/server/bootstrap.js +3 -3
- package/dist/server/roots-manager.js +1 -1
- package/dist/tools/apply-patch.js +19 -8
- package/dist/tools/calculate-hash.js +3 -5
- package/dist/tools/create-directory.d.ts +1 -4
- package/dist/tools/create-directory.js +2 -2
- package/dist/tools/delete-file.js +2 -4
- package/dist/tools/diff-files.js +1 -3
- package/dist/tools/edit-file.d.ts +1 -7
- package/dist/tools/edit-file.js +6 -3
- package/dist/tools/list-directory.js +10 -15
- package/dist/tools/move-file.d.ts +1 -4
- package/dist/tools/move-file.js +15 -27
- package/dist/tools/read-multiple.js +12 -7
- package/dist/tools/read.js +1 -2
- package/dist/tools/replace-in-files.d.ts +1 -10
- package/dist/tools/replace-in-files.js +62 -97
- package/dist/tools/roots.js +2 -6
- package/dist/tools/search-content.js +152 -188
- package/dist/tools/search-files.js +10 -13
- package/dist/tools/shared.d.ts +10 -3
- package/dist/tools/shared.js +53 -12
- package/dist/tools/stat-many.js +6 -4
- package/dist/tools/stat.js +2 -2
- package/dist/tools/task-support.d.ts +0 -7
- package/dist/tools/task-support.js +7 -6
- package/dist/tools/tree.js +1 -1
- package/dist/tools/write-file.js +1 -5
- package/dist/tools.d.ts +0 -1
- package/dist/tools.js +0 -1
- package/package.json +5 -5
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
|
-
import { performance } from 'node:perf_hooks';
|
|
3
2
|
import RE2 from 're2';
|
|
4
3
|
import { DEFAULT_EXCLUDE_PATTERNS } from '../lib/constants.js';
|
|
5
4
|
import { ErrorCode, formatUnknownErrorMessage, McpError, } from '../lib/errors.js';
|
|
6
5
|
import { searchContent } from '../lib/file-operations/search.js';
|
|
7
6
|
import { formatOperationSummary } from '../config.js';
|
|
8
7
|
import { SearchContentInputSchema, SearchContentOutputSchema, } from '../schemas.js';
|
|
9
|
-
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
|
+
import { buildResourceLink, buildToolErrorResponse, buildToolResponse, createToolProgressSession, executeToolWithDiagnostics, READ_ONLY_TOOL_ANNOTATIONS, resolveFinalProgressCurrent, resolvePathOrRoot, truncateProgressPattern, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
10
9
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
11
10
|
/**
|
|
12
11
|
* Configuration constants for the Search Content tool.
|
|
@@ -19,13 +18,11 @@ const CONFIG = {
|
|
|
19
18
|
maxFiles: 'max files',
|
|
20
19
|
},
|
|
21
20
|
};
|
|
22
|
-
let searchMetricSequence = 0;
|
|
23
21
|
const TRUTHY_SUMMARY_FIELDS = [
|
|
24
22
|
'filesMatched',
|
|
25
23
|
'skippedTooLarge',
|
|
26
24
|
'skippedBinary',
|
|
27
25
|
'skippedInaccessible',
|
|
28
|
-
'linesSkippedDueToRegexTimeout',
|
|
29
26
|
];
|
|
30
27
|
function buildStructuredSummaryFields(summary) {
|
|
31
28
|
const result = {};
|
|
@@ -53,15 +50,6 @@ function buildCompletionSuffix(count, filesMatched, scope, stoppedReason) {
|
|
|
53
50
|
: '';
|
|
54
51
|
return `${count} ${matchWord} in ${filesMatched} ${fileWord}${reasonSuffix}`;
|
|
55
52
|
}
|
|
56
|
-
function createSearchMetricNames() {
|
|
57
|
-
searchMetricSequence += 1;
|
|
58
|
-
const metricSuffix = `${Date.now()}_${searchMetricSequence}`;
|
|
59
|
-
return {
|
|
60
|
-
timerStartName: `searchContentStart_${metricSuffix}`,
|
|
61
|
-
timerEndName: `searchContentEnd_${metricSuffix}`,
|
|
62
|
-
metricName: `searchContent_${metricSuffix}`,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
53
|
function compareNormalizedMatches(left, right) {
|
|
66
54
|
const fileCompare = left.relativeFile.localeCompare(right.relativeFile);
|
|
67
55
|
if (fileCompare !== 0)
|
|
@@ -80,7 +68,7 @@ function buildSearchPreviewState(matches, payloads) {
|
|
|
80
68
|
needsExternalize,
|
|
81
69
|
visibleMatches,
|
|
82
70
|
visiblePayloads: payloads.slice(0, visibleCount),
|
|
83
|
-
heading:
|
|
71
|
+
heading: buildHeading(matches.length, visibleMatches.length),
|
|
84
72
|
};
|
|
85
73
|
}
|
|
86
74
|
export const SEARCH_CONTENT_TOOL = {
|
|
@@ -94,187 +82,164 @@ export const SEARCH_CONTENT_TOOL = {
|
|
|
94
82
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
95
83
|
nuances: [
|
|
96
84
|
'Inline results capped at 50 matches; full results via `resourceUri`.',
|
|
97
|
-
'Skips binary and oversized files.',
|
|
98
85
|
],
|
|
99
86
|
gotchas: [
|
|
100
87
|
'Skips binary/oversized files silently — verify with `stat` if no matches.',
|
|
101
88
|
],
|
|
102
89
|
taskSupport: 'optional',
|
|
103
90
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
91
|
+
function buildHeading(totalMatches, visibleMatches) {
|
|
92
|
+
if (visibleMatches >= totalMatches) {
|
|
93
|
+
return `Found ${totalMatches}:`;
|
|
94
|
+
}
|
|
95
|
+
return `Found ${totalMatches} (showing first ${visibleMatches}):`;
|
|
96
|
+
}
|
|
97
|
+
function buildSearchText(heading, matches, summary) {
|
|
98
|
+
if (matches.length === 0)
|
|
99
|
+
return 'No matches';
|
|
100
|
+
const text = buildMatchList(heading, matches);
|
|
101
|
+
if (!summary)
|
|
102
|
+
return text;
|
|
103
|
+
const summaryOpts = {
|
|
104
|
+
truncated: summary.truncated,
|
|
105
|
+
...(summary.truncated
|
|
106
|
+
? { truncatedReason: resolveTruncatedReason(summary) }
|
|
107
|
+
: {}),
|
|
108
|
+
};
|
|
109
|
+
return text + formatOperationSummary(summaryOpts);
|
|
110
|
+
}
|
|
111
|
+
function buildSearchStructured(summary, matches) {
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
matches,
|
|
115
|
+
totalMatches: summary.matches,
|
|
116
|
+
filesScanned: summary.filesScanned,
|
|
117
|
+
...buildStructuredSummaryFields(summary),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function normalizeMatches(result) {
|
|
121
|
+
const relativeByFile = new Map();
|
|
122
|
+
const getRelativeFile = (file) => {
|
|
123
|
+
const cached = relativeByFile.get(file);
|
|
124
|
+
if (cached !== undefined)
|
|
125
|
+
return cached;
|
|
126
|
+
const relative = path.relative(result.basePath, file);
|
|
127
|
+
relativeByFile.set(file, relative);
|
|
128
|
+
return relative;
|
|
129
|
+
};
|
|
130
|
+
return result.matches
|
|
131
|
+
.map((match, index) => ({
|
|
132
|
+
...match,
|
|
133
|
+
relativeFile: getRelativeFile(match.file),
|
|
134
|
+
index,
|
|
135
|
+
}))
|
|
136
|
+
.sort(compareNormalizedMatches);
|
|
137
|
+
}
|
|
138
|
+
function buildMatchPayloads(matches, context) {
|
|
139
|
+
return matches.map((match) => {
|
|
140
|
+
const column = findColumnOffset(match.content, context);
|
|
129
141
|
return {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
normalizeMatches(result) {
|
|
140
|
-
const relativeByFile = new Map();
|
|
141
|
-
const getRelativeFile = (file) => {
|
|
142
|
-
const cached = relativeByFile.get(file);
|
|
143
|
-
if (cached !== undefined)
|
|
144
|
-
return cached;
|
|
145
|
-
const relative = path.relative(result.basePath, file);
|
|
146
|
-
relativeByFile.set(file, relative);
|
|
147
|
-
return relative;
|
|
142
|
+
file: match.relativeFile,
|
|
143
|
+
line: match.line,
|
|
144
|
+
...(column !== undefined ? { column } : {}),
|
|
145
|
+
content: match.content,
|
|
146
|
+
matchCount: match.matchCount,
|
|
147
|
+
...(match.contextBefore
|
|
148
|
+
? { contextBefore: [...match.contextBefore] }
|
|
149
|
+
: {}),
|
|
150
|
+
...(match.contextAfter ? { contextAfter: [...match.contextAfter] } : {}),
|
|
148
151
|
};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
},
|
|
175
|
-
buildMatchList(heading, matches) {
|
|
176
|
-
if (matches.length === 0)
|
|
177
|
-
return heading;
|
|
178
|
-
const parts = [heading];
|
|
179
|
-
for (const match of matches) {
|
|
180
|
-
parts.push(`\n ${match.relativeFile}:${String(match.line).padStart(4)}: ${match.content}`);
|
|
181
|
-
}
|
|
182
|
-
return parts.join('');
|
|
183
|
-
},
|
|
184
|
-
resolveTruncatedReason(summary) {
|
|
185
|
-
if (summary.stoppedReason === 'timeout')
|
|
186
|
-
return 'timeout';
|
|
187
|
-
if (summary.stoppedReason === 'maxFiles') {
|
|
188
|
-
return `max files (${summary.filesScanned})`;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function buildMatchList(heading, matches) {
|
|
155
|
+
if (matches.length === 0)
|
|
156
|
+
return heading;
|
|
157
|
+
const parts = [heading];
|
|
158
|
+
for (const match of matches) {
|
|
159
|
+
parts.push(`\n ${match.relativeFile}:${String(match.line).padStart(4)}: ${match.content}`);
|
|
160
|
+
}
|
|
161
|
+
return parts.join('');
|
|
162
|
+
}
|
|
163
|
+
function resolveTruncatedReason(summary) {
|
|
164
|
+
if (summary.stoppedReason === 'timeout')
|
|
165
|
+
return 'timeout';
|
|
166
|
+
if (summary.stoppedReason === 'maxFiles') {
|
|
167
|
+
return `max files (${summary.filesScanned})`;
|
|
168
|
+
}
|
|
169
|
+
return `max results (${summary.matches})`;
|
|
170
|
+
}
|
|
171
|
+
function findColumnOffset(content, context) {
|
|
172
|
+
try {
|
|
173
|
+
if (context.matcher) {
|
|
174
|
+
context.matcher.lastIndex = 0;
|
|
175
|
+
const match = context.matcher.exec(content);
|
|
176
|
+
return match ? match.index : undefined;
|
|
189
177
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
findColumnOffset(content, context) {
|
|
193
|
-
try {
|
|
194
|
-
if (context.matcher) {
|
|
195
|
-
context.matcher.lastIndex = 0;
|
|
196
|
-
const match = context.matcher.exec(content);
|
|
197
|
-
return match ? match.index : undefined;
|
|
198
|
-
}
|
|
199
|
-
if (context.caseSensitive) {
|
|
200
|
-
const idx = content.indexOf(context.pattern);
|
|
201
|
-
return idx >= 0 ? idx : undefined;
|
|
202
|
-
}
|
|
203
|
-
// Case-insensitive literal search
|
|
204
|
-
const idx = content.toLowerCase().indexOf(context.foldedPattern ?? '');
|
|
178
|
+
if (context.caseSensitive) {
|
|
179
|
+
const idx = content.indexOf(context.pattern);
|
|
205
180
|
return idx >= 0 ? idx : undefined;
|
|
206
181
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
throw new McpError(ErrorCode.E_INVALID_PATTERN, error.message);
|
|
236
|
-
}
|
|
237
|
-
throw error;
|
|
238
|
-
}
|
|
239
|
-
finally {
|
|
240
|
-
performance.mark(timerEndName);
|
|
241
|
-
performance.measure(metricName, timerStartName, timerEndName);
|
|
242
|
-
performance.clearMarks(timerStartName);
|
|
243
|
-
performance.clearMarks(timerEndName);
|
|
244
|
-
performance.clearMeasures(metricName);
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
createMatcher(args) {
|
|
248
|
-
if (!args.isRegex)
|
|
249
|
-
return undefined;
|
|
250
|
-
try {
|
|
251
|
-
const flags = (args.caseSensitive ? '' : 'i') + (args.multiline ? 'm' : '');
|
|
252
|
-
return new RE2(args.pattern, flags);
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
throw new McpError(ErrorCode.E_INVALID_PATTERN, `Invalid regex pattern: ${formatUnknownErrorMessage(error)}`);
|
|
182
|
+
// Case-insensitive literal search
|
|
183
|
+
const idx = content.toLowerCase().indexOf(context.foldedPattern ?? '');
|
|
184
|
+
return idx >= 0 ? idx : undefined;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function executeSearch(args, basePath, signal, onProgress) {
|
|
191
|
+
const excludePatterns = args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS;
|
|
192
|
+
const options = {
|
|
193
|
+
includeHidden: args.includeHidden,
|
|
194
|
+
excludePatterns,
|
|
195
|
+
filePattern: args.filePattern,
|
|
196
|
+
caseSensitive: args.caseSensitive,
|
|
197
|
+
wholeWord: args.wholeWord,
|
|
198
|
+
contextLines: args.contextLines,
|
|
199
|
+
maxResults: args.maxResults,
|
|
200
|
+
isLiteral: !args.isRegex,
|
|
201
|
+
...(signal ? { signal } : {}),
|
|
202
|
+
...(onProgress ? { onProgress } : {}),
|
|
203
|
+
};
|
|
204
|
+
try {
|
|
205
|
+
return await searchContent(basePath, args.pattern, options);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (error instanceof Error && /regular expression/i.test(error.message)) {
|
|
209
|
+
throw new McpError(ErrorCode.E_INVALID_PATTERN, error.message);
|
|
256
210
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
};
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function createSearchMatcher(args) {
|
|
215
|
+
if (!args.isRegex)
|
|
216
|
+
return undefined;
|
|
217
|
+
try {
|
|
218
|
+
const flags = args.caseSensitive ? '' : 'i';
|
|
219
|
+
return new RE2(args.pattern, flags);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
throw new McpError(ErrorCode.E_INVALID_PATTERN, `Invalid regex pattern: ${formatUnknownErrorMessage(error)}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function createSearchContext(args, matcher) {
|
|
226
|
+
return {
|
|
227
|
+
pattern: args.pattern,
|
|
228
|
+
caseSensitive: args.caseSensitive,
|
|
229
|
+
...(matcher ? { matcher } : {}),
|
|
230
|
+
...(!args.isRegex && !args.caseSensitive
|
|
231
|
+
? { foldedPattern: args.pattern.toLowerCase() }
|
|
232
|
+
: {}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
269
235
|
async function handleSearchContent(args, signal, resourceStore, onProgress) {
|
|
270
236
|
const basePath = resolvePathOrRoot(args.path);
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
const
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
const fullStructured = SearchResponseBuilder.buildStructured(result.summary, matchPayloads, { patternType, caseSensitive: args.caseSensitive });
|
|
237
|
+
const regexMatcher = createSearchMatcher(args);
|
|
238
|
+
const result = await executeSearch(args, basePath, signal, onProgress);
|
|
239
|
+
const normalizedMatches = normalizeMatches(result);
|
|
240
|
+
const searchContext = createSearchContext(args, regexMatcher);
|
|
241
|
+
const matchPayloads = buildMatchPayloads(normalizedMatches, searchContext);
|
|
242
|
+
const fullStructured = buildSearchStructured(result.summary, matchPayloads);
|
|
278
243
|
const preview = buildSearchPreviewState(normalizedMatches, matchPayloads);
|
|
279
244
|
if (resourceStore && preview.needsExternalize) {
|
|
280
245
|
const previewStructured = {
|
|
@@ -288,7 +253,7 @@ async function handleSearchContent(args, signal, resourceStore, onProgress) {
|
|
|
288
253
|
text: JSON.stringify(fullStructured),
|
|
289
254
|
});
|
|
290
255
|
previewStructured.resourceUri = entry.uri;
|
|
291
|
-
const text =
|
|
256
|
+
const text = buildSearchText(preview.heading, preview.visibleMatches);
|
|
292
257
|
return buildToolResponse(text, previewStructured, [
|
|
293
258
|
buildResourceLink({
|
|
294
259
|
uri: entry.uri,
|
|
@@ -299,17 +264,18 @@ async function handleSearchContent(args, signal, resourceStore, onProgress) {
|
|
|
299
264
|
}),
|
|
300
265
|
]);
|
|
301
266
|
}
|
|
302
|
-
const text =
|
|
267
|
+
const text = buildSearchText(preview.heading, preview.visibleMatches, result.summary);
|
|
303
268
|
return buildToolResponse(text, fullStructured);
|
|
304
269
|
}
|
|
305
270
|
export function registerSearchContentTool(server, options = {}) {
|
|
306
271
|
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
307
272
|
toolName: 'grep',
|
|
308
273
|
extra,
|
|
274
|
+
outputSchema: SearchContentOutputSchema,
|
|
309
275
|
context: { path: args.path ?? '.' },
|
|
310
276
|
run: async (signal) => {
|
|
311
277
|
const { pattern, filePattern: scope } = args;
|
|
312
|
-
const progressLabel = `🔎︎ grep: ${pattern}`;
|
|
278
|
+
const progressLabel = `🔎︎ grep: ${truncateProgressPattern(pattern)}`;
|
|
313
279
|
const progress = createToolProgressSession(extra, progressLabel);
|
|
314
280
|
const progressWithMessage = ({ current, total, }) => {
|
|
315
281
|
progress.update({
|
|
@@ -321,10 +287,8 @@ export function registerSearchContentTool(server, options = {}) {
|
|
|
321
287
|
try {
|
|
322
288
|
const result = await handleSearchContent(args, signal, options.resourceStore, progressWithMessage);
|
|
323
289
|
const sc = result.structuredContent;
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
const stoppedReason = sc.ok ? sc.stoppedReason : undefined;
|
|
327
|
-
const suffix = buildCompletionSuffix(count, filesMatched, scope, stoppedReason);
|
|
290
|
+
const { totalMatches = 0, filesMatched = 0, stoppedReason } = sc;
|
|
291
|
+
const suffix = buildCompletionSuffix(totalMatches, filesMatched, scope, stoppedReason);
|
|
328
292
|
const finalCurrent = resolveFinalProgressCurrent(progress, (sc.filesScanned ?? 0) + 1);
|
|
329
293
|
progress.complete(`${progressLabel} • ${suffix}`, finalCurrent);
|
|
330
294
|
return result;
|
|
@@ -4,7 +4,7 @@ import { ErrorCode } from '../lib/errors.js';
|
|
|
4
4
|
import { searchFiles } from '../lib/file-operations/search.js';
|
|
5
5
|
import { formatOperationSummary, joinLines } from '../config.js';
|
|
6
6
|
import { SearchFilesInputSchema, SearchFilesOutputSchema } from '../schemas.js';
|
|
7
|
-
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, decodeOffsetCursor, encodeOffsetCursor, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
7
|
+
import { buildToolErrorResponse, buildToolResponse, createProgressReporter, decodeOffsetCursor, encodeOffsetCursor, executeToolWithDiagnostics, notifyProgress, READ_ONLY_TOOL_ANNOTATIONS, resolvePathOrRoot, truncateProgressPattern, withDefaultIcons, withValidatedArgs, wrapToolHandler, } from './shared.js';
|
|
8
8
|
import { registerToolTaskIfAvailable } from './task-support.js';
|
|
9
9
|
export const SEARCH_FILES_TOOL = {
|
|
10
10
|
name: 'find',
|
|
@@ -14,10 +14,7 @@ export const SEARCH_FILES_TOOL = {
|
|
|
14
14
|
inputSchema: SearchFilesInputSchema,
|
|
15
15
|
outputSchema: SearchFilesOutputSchema,
|
|
16
16
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
|
17
|
-
nuances: [
|
|
18
|
-
'Respects `.gitignore` unless `includeIgnored=true`.',
|
|
19
|
-
'Returns relative paths plus metadata; may truncate.',
|
|
20
|
-
],
|
|
17
|
+
nuances: ['Respects `.gitignore` unless `includeIgnored=true`.'],
|
|
21
18
|
taskSupport: 'optional',
|
|
22
19
|
};
|
|
23
20
|
async function handleSearchFiles(args, signal, onProgress) {
|
|
@@ -52,7 +49,6 @@ async function handleSearchFiles(args, signal, onProgress) {
|
|
|
52
49
|
const structured = {
|
|
53
50
|
ok: true,
|
|
54
51
|
root: basePath,
|
|
55
|
-
pattern: args.pattern,
|
|
56
52
|
results: relativeResults,
|
|
57
53
|
totalMatches: result.summary.matched,
|
|
58
54
|
filesScanned: result.summary.filesScanned,
|
|
@@ -103,17 +99,19 @@ export function registerSearchFilesTool(server, options = {}) {
|
|
|
103
99
|
const handler = (args, extra) => executeToolWithDiagnostics({
|
|
104
100
|
toolName: 'find',
|
|
105
101
|
extra,
|
|
102
|
+
outputSchema: SearchFilesOutputSchema,
|
|
106
103
|
timedSignal: { timeoutMs: DEFAULT_SEARCH_TIMEOUT_MS },
|
|
107
104
|
context: { path: args.path ?? '.' },
|
|
108
105
|
run: async (signal) => {
|
|
109
106
|
const rawScopeLabel = args.path ? path.basename(args.path) : '.';
|
|
110
107
|
const scopeLabel = rawScopeLabel || '.';
|
|
111
108
|
const { pattern } = args;
|
|
112
|
-
const
|
|
109
|
+
const truncatedPattern = truncateProgressPattern(pattern);
|
|
110
|
+
const context = `${truncatedPattern} in ${scopeLabel}`;
|
|
113
111
|
let progressCursor = 0;
|
|
114
112
|
notifyProgress(extra, {
|
|
115
113
|
current: 0,
|
|
116
|
-
message: `🔎︎ find: ${
|
|
114
|
+
message: `🔎︎ find: ${truncatedPattern}`,
|
|
117
115
|
});
|
|
118
116
|
const baseReporter = createProgressReporter(extra);
|
|
119
117
|
const progressWithMessage = ({ current, total, }) => {
|
|
@@ -122,20 +120,19 @@ export function registerSearchFilesTool(server, options = {}) {
|
|
|
122
120
|
baseReporter({
|
|
123
121
|
current,
|
|
124
122
|
...(total !== undefined ? { total } : {}),
|
|
125
|
-
message: `🔎︎ find: ${
|
|
123
|
+
message: `🔎︎ find: ${truncatedPattern} [${current} files]`,
|
|
126
124
|
});
|
|
127
125
|
};
|
|
128
126
|
try {
|
|
129
127
|
const result = await handleSearchFiles(args, signal, progressWithMessage);
|
|
130
128
|
const sc = result.structuredContent;
|
|
131
|
-
const
|
|
132
|
-
const stoppedReason = sc.ok ? sc.stoppedReason : undefined;
|
|
129
|
+
const { totalMatches = 0, stoppedReason } = sc;
|
|
133
130
|
let suffix;
|
|
134
|
-
if (
|
|
131
|
+
if (totalMatches === 0) {
|
|
135
132
|
suffix = 'No matches';
|
|
136
133
|
}
|
|
137
134
|
else {
|
|
138
|
-
suffix = `${
|
|
135
|
+
suffix = `${totalMatches} ${totalMatches === 1 ? 'match' : 'matches'}`;
|
|
139
136
|
if (stoppedReason === 'timeout') {
|
|
140
137
|
suffix += ' [timeout]';
|
|
141
138
|
}
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -22,7 +22,6 @@ export declare const IDEMPOTENT_WRITE_TOOL_ANNOTATIONS: {
|
|
|
22
22
|
readonly destructiveHint: false;
|
|
23
23
|
readonly openWorldHint: false;
|
|
24
24
|
};
|
|
25
|
-
export declare function shouldStripStructuredOutput(): boolean;
|
|
26
25
|
export declare function maybeStripStructuredContentFromResult<T extends object>(result: T): T;
|
|
27
26
|
type ResourceEntry = ReturnType<ResourceStore['putText']>;
|
|
28
27
|
export declare function maybeExternalizeTextContent(resourceStore: ResourceStore | undefined, content: string, params: {
|
|
@@ -39,6 +38,12 @@ export declare function buildResourceLink(params: {
|
|
|
39
38
|
description?: string;
|
|
40
39
|
expiresAt?: string;
|
|
41
40
|
}): ContentBlock;
|
|
41
|
+
export declare function buildStructuredError(error: unknown, defaultCode: ErrorCode, path?: string): {
|
|
42
|
+
code: ErrorCode;
|
|
43
|
+
message: string;
|
|
44
|
+
path?: string;
|
|
45
|
+
suggestion?: string;
|
|
46
|
+
};
|
|
42
47
|
export declare function buildToolResponse<T>(text: string, structuredContent: T, extraContent?: ContentBlock[]): {
|
|
43
48
|
content: ContentBlock[];
|
|
44
49
|
structuredContent: T;
|
|
@@ -95,6 +100,7 @@ export declare function buildFileInfoPayload(info: FileInfo): FileInfoPayload;
|
|
|
95
100
|
interface ToolExecutionOptions<T> {
|
|
96
101
|
toolName: string;
|
|
97
102
|
extra: ToolExtra;
|
|
103
|
+
outputSchema?: z.ZodType<T>;
|
|
98
104
|
run: (signal: AbortSignal | undefined) => ToolResponse<T> | Promise<ToolResponse<T>>;
|
|
99
105
|
onError: (error: unknown) => ToolResult<T>;
|
|
100
106
|
context?: Record<string, unknown>;
|
|
@@ -114,7 +120,7 @@ export declare function notifyProgress(extra: ToolExtra, progress: {
|
|
|
114
120
|
total?: number;
|
|
115
121
|
message?: string;
|
|
116
122
|
}): void;
|
|
117
|
-
|
|
123
|
+
interface ToolProgressSession {
|
|
118
124
|
update: (progress: {
|
|
119
125
|
current: number;
|
|
120
126
|
total?: number;
|
|
@@ -125,7 +131,7 @@ export interface ToolProgressSession {
|
|
|
125
131
|
fail: (message: string, minimumCurrent?: number) => void;
|
|
126
132
|
getCurrent: () => number;
|
|
127
133
|
}
|
|
128
|
-
|
|
134
|
+
interface BatchProgressCallbacks {
|
|
129
135
|
progress: ToolProgressSession;
|
|
130
136
|
onItemComplete: () => void;
|
|
131
137
|
}
|
|
@@ -155,6 +161,7 @@ export declare function resolvePathOrRoot(pathValue: string | undefined): string
|
|
|
155
161
|
export declare function encodeOffsetCursor(offset: number): string;
|
|
156
162
|
export declare function decodeOffsetCursor(cursor: string): number;
|
|
157
163
|
export declare function buildBatchPathContext(paths: readonly string[], unitLabel?: string): string;
|
|
164
|
+
export declare function truncateProgressPattern(pattern: string, maxLength?: number): string;
|
|
158
165
|
export declare function buildBatchCompletionSuffix(summary: {
|
|
159
166
|
total?: number;
|
|
160
167
|
failed?: number;
|