@j0hanz/fs-context-mcp 2.0.7 → 2.1.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 +60 -12
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/instructions.md +27 -118
- package/dist/lib/constants.d.ts +1 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +1 -0
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/file-operations/file-info.d.ts.map +1 -1
- package/dist/lib/file-operations/file-info.js +2 -0
- package/dist/lib/file-operations/file-info.js.map +1 -1
- package/dist/lib/file-operations/gitignore.d.ts +6 -0
- package/dist/lib/file-operations/gitignore.d.ts.map +1 -0
- package/dist/lib/file-operations/gitignore.js +42 -0
- package/dist/lib/file-operations/gitignore.js.map +1 -0
- package/dist/lib/file-operations/read-multiple-files.d.ts +5 -1
- package/dist/lib/file-operations/read-multiple-files.d.ts.map +1 -1
- package/dist/lib/file-operations/read-multiple-files.js +48 -17
- package/dist/lib/file-operations/read-multiple-files.js.map +1 -1
- package/dist/lib/file-operations/search-content-scan.d.ts +29 -0
- package/dist/lib/file-operations/search-content-scan.d.ts.map +1 -0
- package/dist/lib/file-operations/search-content-scan.js +468 -0
- package/dist/lib/file-operations/search-content-scan.js.map +1 -0
- package/dist/lib/file-operations/search-content-worker-api.d.ts +56 -0
- package/dist/lib/file-operations/search-content-worker-api.d.ts.map +1 -0
- package/dist/lib/file-operations/search-content-worker-api.js +239 -0
- package/dist/lib/file-operations/search-content-worker-api.js.map +1 -0
- package/dist/lib/file-operations/search-content.d.ts.map +1 -1
- package/dist/lib/file-operations/search-content.js +59 -53
- package/dist/lib/file-operations/search-content.js.map +1 -1
- package/dist/lib/file-operations/search-files.d.ts +1 -0
- package/dist/lib/file-operations/search-files.d.ts.map +1 -1
- package/dist/lib/file-operations/search-files.js +11 -2
- package/dist/lib/file-operations/search-files.js.map +1 -1
- package/dist/lib/file-operations/tree.d.ts +25 -0
- package/dist/lib/file-operations/tree.d.ts.map +1 -0
- package/dist/lib/file-operations/tree.js +172 -0
- package/dist/lib/file-operations/tree.js.map +1 -0
- package/dist/lib/fs-helpers.d.ts +5 -1
- package/dist/lib/fs-helpers.d.ts.map +1 -1
- package/dist/lib/fs-helpers.js +131 -14
- package/dist/lib/fs-helpers.js.map +1 -1
- package/dist/lib/path-validation.d.ts.map +1 -1
- package/dist/lib/path-validation.js +15 -17
- package/dist/lib/path-validation.js.map +1 -1
- package/dist/lib/resource-store.d.ts +23 -0
- package/dist/lib/resource-store.d.ts.map +1 -0
- package/dist/lib/resource-store.js +75 -0
- package/dist/lib/resource-store.js.map +1 -0
- package/dist/lib/strict-stdio-transport.d.ts +31 -0
- package/dist/lib/strict-stdio-transport.d.ts.map +1 -0
- package/dist/lib/strict-stdio-transport.js +158 -0
- package/dist/lib/strict-stdio-transport.js.map +1 -0
- package/dist/resources.d.ts +5 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +43 -0
- package/dist/resources.js.map +1 -0
- package/dist/schemas.d.ts +78 -12
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +125 -24
- package/dist/schemas.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +52 -11
- package/dist/server.js.map +1 -1
- package/dist/tooling/tool-response.d.ts +32 -0
- package/dist/tooling/tool-response.d.ts.map +1 -0
- package/dist/tooling/tool-response.js +63 -0
- package/dist/tooling/tool-response.js.map +1 -0
- package/dist/tooling/tools-registry.d.ts +5 -0
- package/dist/tooling/tools-registry.d.ts.map +1 -0
- package/dist/tooling/tools-registry.js +532 -0
- package/dist/tooling/tools-registry.js.map +1 -0
- package/dist/tools.d.ts +12 -12
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +351 -51
- package/dist/tools.js.map +1 -1
- package/package.json +7 -5
package/dist/tools.js
CHANGED
|
@@ -7,15 +7,34 @@ import { listDirectory } from './lib/file-operations/list-directory.js';
|
|
|
7
7
|
import { readMultipleFiles } from './lib/file-operations/read-multiple-files.js';
|
|
8
8
|
import { searchContent } from './lib/file-operations/search-content.js';
|
|
9
9
|
import { searchFiles } from './lib/file-operations/search-files.js';
|
|
10
|
+
import { formatTreeAscii, treeDirectory } from './lib/file-operations/tree.js';
|
|
10
11
|
import { createTimedAbortSignal, readFile } from './lib/fs-helpers.js';
|
|
11
12
|
import { withToolDiagnostics } from './lib/observability.js';
|
|
12
13
|
import { getAllowedDirectories } from './lib/path-validation.js';
|
|
13
|
-
import { GetFileInfoInputSchema, GetFileInfoOutputSchema, GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, ListAllowedDirectoriesInputSchema, ListAllowedDirectoriesOutputSchema, ListDirectoryInputSchema, ListDirectoryOutputSchema, ReadFileInputSchema, ReadFileOutputSchema, ReadMultipleFilesInputSchema, ReadMultipleFilesOutputSchema, SearchContentInputSchema, SearchContentOutputSchema, SearchFilesInputSchema, SearchFilesOutputSchema, } from './schemas.js';
|
|
14
|
-
|
|
14
|
+
import { GetFileInfoInputSchema, GetFileInfoOutputSchema, GetMultipleFileInfoInputSchema, GetMultipleFileInfoOutputSchema, ListAllowedDirectoriesInputSchema, ListAllowedDirectoriesOutputSchema, ListDirectoryInputSchema, ListDirectoryOutputSchema, ReadFileInputSchema, ReadFileOutputSchema, ReadMultipleFilesInputSchema, ReadMultipleFilesOutputSchema, SearchContentInputSchema, SearchContentOutputSchema, SearchFilesInputSchema, SearchFilesOutputSchema, TreeInputSchema, TreeOutputSchema, } from './schemas.js';
|
|
15
|
+
const MAX_INLINE_CONTENT_CHARS = 20_000;
|
|
16
|
+
const MAX_INLINE_PREVIEW_CHARS = 4_000;
|
|
17
|
+
const MAX_INLINE_MATCHES = 50;
|
|
18
|
+
function buildTextPreview(text) {
|
|
19
|
+
if (text.length <= MAX_INLINE_PREVIEW_CHARS)
|
|
20
|
+
return text;
|
|
21
|
+
return `${text.slice(0, MAX_INLINE_PREVIEW_CHARS)}\n… [truncated preview]`;
|
|
22
|
+
}
|
|
23
|
+
function buildResourceLink(params) {
|
|
24
|
+
return {
|
|
25
|
+
type: 'resource_link',
|
|
26
|
+
uri: params.uri,
|
|
27
|
+
name: params.name,
|
|
28
|
+
...(params.description ? { description: params.description } : {}),
|
|
29
|
+
...(params.mimeType ? { mimeType: params.mimeType } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function buildContentBlock(text, structuredContent, extraContent = []) {
|
|
15
33
|
const json = JSON.stringify(structuredContent);
|
|
16
34
|
return {
|
|
17
35
|
content: [
|
|
18
36
|
{ type: 'text', text },
|
|
37
|
+
...extraContent,
|
|
19
38
|
{ type: 'text', text: json },
|
|
20
39
|
],
|
|
21
40
|
structuredContent,
|
|
@@ -29,9 +48,10 @@ function resolveDetailedError(error, defaultCode, path) {
|
|
|
29
48
|
}
|
|
30
49
|
return detailed;
|
|
31
50
|
}
|
|
32
|
-
export function buildToolResponse(text, structuredContent) {
|
|
33
|
-
return buildContentBlock(text, structuredContent);
|
|
51
|
+
export function buildToolResponse(text, structuredContent, extraContent = []) {
|
|
52
|
+
return buildContentBlock(text, structuredContent, extraContent);
|
|
34
53
|
}
|
|
54
|
+
const NOT_INITIALIZED_ERROR = new McpError(ErrorCode.E_INVALID_INPUT, 'Client not initialized; wait for notifications/initialized');
|
|
35
55
|
async function withToolErrorHandling(run, onError) {
|
|
36
56
|
try {
|
|
37
57
|
return await run();
|
|
@@ -62,6 +82,82 @@ export function buildToolErrorResponse(error, defaultCode, path) {
|
|
|
62
82
|
isError: true,
|
|
63
83
|
};
|
|
64
84
|
}
|
|
85
|
+
function buildNotInitializedResult() {
|
|
86
|
+
return buildToolErrorResponse(NOT_INITIALIZED_ERROR, ErrorCode.E_INVALID_INPUT);
|
|
87
|
+
}
|
|
88
|
+
async function sendProgressNotification(extra, params) {
|
|
89
|
+
if (!extra.sendNotification)
|
|
90
|
+
return;
|
|
91
|
+
try {
|
|
92
|
+
await extra.sendNotification({
|
|
93
|
+
method: 'notifications/progress',
|
|
94
|
+
params,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Ignore progress notification failures to avoid breaking tool execution.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function resolveToolOk(result) {
|
|
102
|
+
if (!result || typeof result !== 'object')
|
|
103
|
+
return true;
|
|
104
|
+
const typed = result;
|
|
105
|
+
if (typed.isError === true)
|
|
106
|
+
return false;
|
|
107
|
+
const structured = typed.structuredContent;
|
|
108
|
+
if (structured &&
|
|
109
|
+
typeof structured === 'object' &&
|
|
110
|
+
'ok' in structured &&
|
|
111
|
+
typeof structured.ok === 'boolean') {
|
|
112
|
+
return Boolean(structured.ok);
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
async function withProgress(tool, extra, run) {
|
|
117
|
+
const token = extra._meta?.progressToken;
|
|
118
|
+
if (!token) {
|
|
119
|
+
return await run();
|
|
120
|
+
}
|
|
121
|
+
const total = 1;
|
|
122
|
+
await sendProgressNotification(extra, {
|
|
123
|
+
progressToken: token,
|
|
124
|
+
progress: 0,
|
|
125
|
+
total,
|
|
126
|
+
message: `${tool} started`,
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
const result = await run();
|
|
130
|
+
const ok = resolveToolOk(result);
|
|
131
|
+
await sendProgressNotification(extra, {
|
|
132
|
+
progressToken: token,
|
|
133
|
+
progress: total,
|
|
134
|
+
total,
|
|
135
|
+
message: ok ? `${tool} completed` : `${tool} failed`,
|
|
136
|
+
});
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
await sendProgressNotification(extra, {
|
|
141
|
+
progressToken: token,
|
|
142
|
+
progress: total,
|
|
143
|
+
total,
|
|
144
|
+
message: `${tool} failed`,
|
|
145
|
+
});
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function wrapToolHandler(handler, options) {
|
|
150
|
+
return async (args, extra) => {
|
|
151
|
+
const resolvedExtra = extra ?? {};
|
|
152
|
+
if (options.guard && !options.guard()) {
|
|
153
|
+
return buildNotInitializedResult();
|
|
154
|
+
}
|
|
155
|
+
if (options.progressTool) {
|
|
156
|
+
return await withProgress(options.progressTool, resolvedExtra, () => handler(args, resolvedExtra));
|
|
157
|
+
}
|
|
158
|
+
return await handler(args, resolvedExtra);
|
|
159
|
+
};
|
|
160
|
+
}
|
|
65
161
|
function resolvePathOrRoot(pathValue) {
|
|
66
162
|
if (pathValue && pathValue.trim().length > 0)
|
|
67
163
|
return pathValue;
|
|
@@ -133,6 +229,7 @@ function buildStructuredSearchResult(result) {
|
|
|
133
229
|
file: match.relativeFile,
|
|
134
230
|
line: match.line,
|
|
135
231
|
content: match.content,
|
|
232
|
+
matchCount: match.matchCount,
|
|
136
233
|
...(match.contextBefore
|
|
137
234
|
? { contextBefore: [...match.contextBefore] }
|
|
138
235
|
: {}),
|
|
@@ -171,6 +268,9 @@ function buildFileInfoPayload(info) {
|
|
|
171
268
|
path: info.path,
|
|
172
269
|
type: info.type,
|
|
173
270
|
size: info.size,
|
|
271
|
+
...(info.tokenEstimate !== undefined
|
|
272
|
+
? { tokenEstimate: info.tokenEstimate }
|
|
273
|
+
: {}),
|
|
174
274
|
created: info.created.toISOString(),
|
|
175
275
|
modified: info.modified.toISOString(),
|
|
176
276
|
accessed: info.accessed.toISOString(),
|
|
@@ -238,6 +338,18 @@ const SEARCH_FILES_TOOL = {
|
|
|
238
338
|
openWorldHint: true,
|
|
239
339
|
},
|
|
240
340
|
};
|
|
341
|
+
const TREE_TOOL = {
|
|
342
|
+
title: 'Tree',
|
|
343
|
+
description: 'Render a directory tree (bounded recursion). ' +
|
|
344
|
+
'Returns an ASCII tree for quick scanning and a structured JSON tree for programmatic use.',
|
|
345
|
+
inputSchema: TreeInputSchema,
|
|
346
|
+
outputSchema: TreeOutputSchema,
|
|
347
|
+
annotations: {
|
|
348
|
+
readOnlyHint: true,
|
|
349
|
+
idempotentHint: true,
|
|
350
|
+
openWorldHint: true,
|
|
351
|
+
},
|
|
352
|
+
};
|
|
241
353
|
const READ_FILE_TOOL = {
|
|
242
354
|
title: 'Read File',
|
|
243
355
|
description: 'Read the text contents of a file. ' +
|
|
@@ -317,9 +429,9 @@ function handleListAllowedDirectories() {
|
|
|
317
429
|
};
|
|
318
430
|
return buildToolResponse(buildTextRoots(dirs), structured);
|
|
319
431
|
}
|
|
320
|
-
export function registerListAllowedDirectoriesTool(server) {
|
|
432
|
+
export function registerListAllowedDirectoriesTool(server, options = {}) {
|
|
321
433
|
const handler = () => withToolErrorHandling(() => withToolDiagnostics('roots', () => Promise.resolve(handleListAllowedDirectories())), (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN));
|
|
322
|
-
server.registerTool('roots', LIST_ALLOWED_DIRECTORIES_TOOL, handler);
|
|
434
|
+
server.registerTool('roots', LIST_ALLOWED_DIRECTORIES_TOOL, wrapToolHandler(handler, { guard: options.isInitialized }));
|
|
323
435
|
}
|
|
324
436
|
function buildStructuredListEntry(entry) {
|
|
325
437
|
return {
|
|
@@ -348,14 +460,16 @@ async function handleListDirectory(args, signal) {
|
|
|
348
460
|
const result = await listDirectory(dirPath, options);
|
|
349
461
|
return buildToolResponse(buildListTextResult(result), buildStructuredListResult(result));
|
|
350
462
|
}
|
|
351
|
-
export function registerListDirectoryTool(server) {
|
|
352
|
-
|
|
463
|
+
export function registerListDirectoryTool(server, options = {}) {
|
|
464
|
+
const handler = (args, extra) => withToolDiagnostics('ls', () => withToolErrorHandling(() => handleListDirectory(args, extra.signal), (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, args.path ?? '.')), { path: args.path ?? '.' });
|
|
465
|
+
server.registerTool('ls', LIST_DIRECTORY_TOOL, wrapToolHandler(handler, { guard: options.isInitialized }));
|
|
353
466
|
}
|
|
354
467
|
async function handleSearchFiles(args, signal) {
|
|
355
468
|
const basePath = resolvePathOrRoot(args.path);
|
|
356
469
|
const excludePatterns = args.includeIgnored ? [] : DEFAULT_EXCLUDE_PATTERNS;
|
|
357
470
|
const result = await searchFiles(basePath, args.pattern, excludePatterns, {
|
|
358
471
|
maxResults: args.maxResults,
|
|
472
|
+
respectGitignore: !args.includeIgnored,
|
|
359
473
|
...(signal ? { signal } : {}),
|
|
360
474
|
});
|
|
361
475
|
const relativeResults = result.results.map((entry) => ({
|
|
@@ -369,14 +483,33 @@ async function handleSearchFiles(args, signal) {
|
|
|
369
483
|
totalMatches: result.summary.matched,
|
|
370
484
|
truncated: result.summary.truncated,
|
|
371
485
|
};
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
486
|
+
let truncatedReason;
|
|
487
|
+
if (result.summary.truncated) {
|
|
488
|
+
if (result.summary.stoppedReason === 'timeout') {
|
|
489
|
+
truncatedReason = 'timeout';
|
|
490
|
+
}
|
|
491
|
+
else if (result.summary.stoppedReason === 'maxFiles') {
|
|
492
|
+
truncatedReason = `max files (${result.summary.filesScanned})`;
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
truncatedReason = `max results (${result.summary.matched})`;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const summaryOptions = {
|
|
499
|
+
truncated: result.summary.truncated,
|
|
500
|
+
...(truncatedReason ? { truncatedReason } : {}),
|
|
501
|
+
};
|
|
502
|
+
const textLines = relativeResults.length === 0
|
|
503
|
+
? ['No matches']
|
|
504
|
+
: [
|
|
505
|
+
`Found ${relativeResults.length}:`,
|
|
506
|
+
...relativeResults.map((entry) => ` ${entry.path}`),
|
|
507
|
+
];
|
|
508
|
+
const text = joinLines(textLines) + formatOperationSummary(summaryOptions);
|
|
376
509
|
return buildToolResponse(text, structured);
|
|
377
510
|
}
|
|
378
|
-
function registerSearchFilesTool(server) {
|
|
379
|
-
|
|
511
|
+
function registerSearchFilesTool(server, options = {}) {
|
|
512
|
+
const handler = (args, extra) => withToolDiagnostics('find', () => withToolErrorHandling(async () => {
|
|
380
513
|
const { signal, cleanup } = createTimedAbortSignal(extra.signal, DEFAULT_SEARCH_TIMEOUT_MS);
|
|
381
514
|
try {
|
|
382
515
|
return await handleSearchFiles(args, signal);
|
|
@@ -384,9 +517,52 @@ function registerSearchFilesTool(server) {
|
|
|
384
517
|
finally {
|
|
385
518
|
cleanup();
|
|
386
519
|
}
|
|
387
|
-
}, (error) => buildToolErrorResponse(error, ErrorCode.E_INVALID_PATTERN, args.path)), { path: args.path ?? '.' })
|
|
520
|
+
}, (error) => buildToolErrorResponse(error, ErrorCode.E_INVALID_PATTERN, args.path)), { path: args.path ?? '.' });
|
|
521
|
+
server.registerTool('find', SEARCH_FILES_TOOL, wrapToolHandler(handler, {
|
|
522
|
+
guard: options.isInitialized,
|
|
523
|
+
progressTool: 'find',
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
async function handleTree(args, signal) {
|
|
527
|
+
const basePath = resolvePathOrRoot(args.path);
|
|
528
|
+
const result = await treeDirectory(basePath, {
|
|
529
|
+
maxDepth: args.maxDepth,
|
|
530
|
+
maxEntries: args.maxEntries,
|
|
531
|
+
includeHidden: args.includeHidden,
|
|
532
|
+
includeIgnored: args.includeIgnored,
|
|
533
|
+
...(signal ? { signal } : {}),
|
|
534
|
+
});
|
|
535
|
+
const ascii = formatTreeAscii(result.tree);
|
|
536
|
+
const structured = {
|
|
537
|
+
ok: true,
|
|
538
|
+
root: result.root,
|
|
539
|
+
tree: result.tree,
|
|
540
|
+
ascii,
|
|
541
|
+
truncated: result.truncated,
|
|
542
|
+
totalEntries: result.totalEntries,
|
|
543
|
+
};
|
|
544
|
+
const text = result.truncated ? `${ascii}\n[truncated]` : ascii;
|
|
545
|
+
return buildToolResponse(text, structured);
|
|
388
546
|
}
|
|
389
|
-
|
|
547
|
+
function registerTreeTool(server, options = {}) {
|
|
548
|
+
const handler = (args, extra) => {
|
|
549
|
+
const targetPath = args.path ?? '.';
|
|
550
|
+
return withToolDiagnostics('tree', () => withToolErrorHandling(async () => {
|
|
551
|
+
const { signal, cleanup } = createTimedAbortSignal(extra.signal, DEFAULT_SEARCH_TIMEOUT_MS);
|
|
552
|
+
try {
|
|
553
|
+
return await handleTree(args, signal);
|
|
554
|
+
}
|
|
555
|
+
finally {
|
|
556
|
+
cleanup();
|
|
557
|
+
}
|
|
558
|
+
}, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, targetPath)), { path: targetPath });
|
|
559
|
+
};
|
|
560
|
+
server.registerTool('tree', TREE_TOOL, wrapToolHandler(handler, {
|
|
561
|
+
guard: options.isInitialized,
|
|
562
|
+
progressTool: 'tree',
|
|
563
|
+
}));
|
|
564
|
+
}
|
|
565
|
+
async function handleReadFile(args, signal, resourceStore) {
|
|
390
566
|
const options = {
|
|
391
567
|
encoding: 'utf-8',
|
|
392
568
|
maxSize: MAX_TEXT_FILE_SIZE,
|
|
@@ -395,6 +571,12 @@ async function handleReadFile(args, signal) {
|
|
|
395
571
|
if (args.head !== undefined) {
|
|
396
572
|
options.head = args.head;
|
|
397
573
|
}
|
|
574
|
+
if (args.startLine !== undefined) {
|
|
575
|
+
options.startLine = args.startLine;
|
|
576
|
+
}
|
|
577
|
+
if (args.endLine !== undefined) {
|
|
578
|
+
options.endLine = args.endLine;
|
|
579
|
+
}
|
|
398
580
|
if (signal) {
|
|
399
581
|
options.signal = signal;
|
|
400
582
|
}
|
|
@@ -404,45 +586,112 @@ async function handleReadFile(args, signal) {
|
|
|
404
586
|
path: args.path,
|
|
405
587
|
content: result.content,
|
|
406
588
|
truncated: result.truncated,
|
|
589
|
+
resourceUri: undefined,
|
|
407
590
|
totalLines: result.totalLines,
|
|
591
|
+
readMode: result.readMode,
|
|
592
|
+
head: result.head,
|
|
593
|
+
startLine: result.startLine,
|
|
594
|
+
endLine: result.endLine,
|
|
595
|
+
linesRead: result.linesRead,
|
|
596
|
+
hasMoreLines: result.hasMoreLines,
|
|
597
|
+
};
|
|
598
|
+
if (!resourceStore || result.content.length <= MAX_INLINE_CONTENT_CHARS) {
|
|
599
|
+
return buildToolResponse(result.content, structured);
|
|
600
|
+
}
|
|
601
|
+
const entry = resourceStore.putText({
|
|
602
|
+
name: `read:${path.basename(args.path)}`,
|
|
603
|
+
mimeType: 'text/plain',
|
|
604
|
+
text: result.content,
|
|
605
|
+
});
|
|
606
|
+
const preview = buildTextPreview(result.content);
|
|
607
|
+
const structuredWithResource = {
|
|
608
|
+
...structured,
|
|
609
|
+
content: preview,
|
|
610
|
+
truncated: true,
|
|
611
|
+
resourceUri: entry.uri,
|
|
408
612
|
};
|
|
409
|
-
|
|
613
|
+
const text = joinLines([
|
|
614
|
+
`Output too large to inline (${result.content.length} chars).`,
|
|
615
|
+
'Preview:',
|
|
616
|
+
preview,
|
|
617
|
+
]);
|
|
618
|
+
return buildToolResponse(text, structuredWithResource, [
|
|
619
|
+
buildResourceLink({
|
|
620
|
+
uri: entry.uri,
|
|
621
|
+
name: entry.name,
|
|
622
|
+
mimeType: entry.mimeType,
|
|
623
|
+
description: 'Full file contents',
|
|
624
|
+
}),
|
|
625
|
+
]);
|
|
410
626
|
}
|
|
411
|
-
function registerReadFileTool(server) {
|
|
627
|
+
function registerReadFileTool(server, options = {}) {
|
|
412
628
|
const handler = (args, extra) => withToolDiagnostics('read', () => withToolErrorHandling(async () => {
|
|
413
629
|
const { signal, cleanup } = createTimedAbortSignal(extra.signal, DEFAULT_SEARCH_TIMEOUT_MS);
|
|
414
630
|
try {
|
|
415
|
-
return await handleReadFile(args, signal);
|
|
631
|
+
return await handleReadFile(args, signal, options.resourceStore);
|
|
416
632
|
}
|
|
417
633
|
finally {
|
|
418
634
|
cleanup();
|
|
419
635
|
}
|
|
420
636
|
}, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FILE, args.path)), { path: args.path });
|
|
421
|
-
server.registerTool('read', READ_FILE_TOOL, handler);
|
|
637
|
+
server.registerTool('read', READ_FILE_TOOL, wrapToolHandler(handler, { guard: options.isInitialized }));
|
|
422
638
|
}
|
|
423
|
-
async function handleReadMultipleFiles(args, signal) {
|
|
639
|
+
async function handleReadMultipleFiles(args, signal, resourceStore) {
|
|
424
640
|
const options = {
|
|
425
641
|
...(signal ? { signal } : {}),
|
|
426
642
|
};
|
|
427
643
|
if (args.head !== undefined) {
|
|
428
644
|
options.head = args.head;
|
|
429
645
|
}
|
|
646
|
+
if (args.startLine !== undefined) {
|
|
647
|
+
options.startLine = args.startLine;
|
|
648
|
+
}
|
|
649
|
+
if (args.endLine !== undefined) {
|
|
650
|
+
options.endLine = args.endLine;
|
|
651
|
+
}
|
|
430
652
|
const results = await readMultipleFiles(args.paths, options);
|
|
653
|
+
const mappedResults = results.map((result) => {
|
|
654
|
+
if (!resourceStore || !result.content) {
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
657
|
+
if (result.content.length <= MAX_INLINE_CONTENT_CHARS) {
|
|
658
|
+
return result;
|
|
659
|
+
}
|
|
660
|
+
const entry = resourceStore.putText({
|
|
661
|
+
name: `read:${path.basename(result.path)}`,
|
|
662
|
+
mimeType: 'text/plain',
|
|
663
|
+
text: result.content,
|
|
664
|
+
});
|
|
665
|
+
return {
|
|
666
|
+
...result,
|
|
667
|
+
content: buildTextPreview(result.content),
|
|
668
|
+
truncated: true,
|
|
669
|
+
resourceUri: entry.uri,
|
|
670
|
+
};
|
|
671
|
+
});
|
|
431
672
|
const structured = {
|
|
432
673
|
ok: true,
|
|
433
|
-
results:
|
|
674
|
+
results: mappedResults.map((result) => ({
|
|
434
675
|
path: result.path,
|
|
435
676
|
content: result.content,
|
|
436
677
|
truncated: result.truncated,
|
|
678
|
+
resourceUri: result.resourceUri,
|
|
679
|
+
readMode: result.readMode,
|
|
680
|
+
head: result.head,
|
|
681
|
+
startLine: result.startLine,
|
|
682
|
+
endLine: result.endLine,
|
|
683
|
+
linesRead: result.linesRead,
|
|
684
|
+
hasMoreLines: result.hasMoreLines,
|
|
685
|
+
totalLines: result.totalLines,
|
|
437
686
|
error: result.error,
|
|
438
687
|
})),
|
|
439
688
|
summary: {
|
|
440
|
-
total:
|
|
441
|
-
succeeded:
|
|
442
|
-
failed:
|
|
689
|
+
total: mappedResults.length,
|
|
690
|
+
succeeded: mappedResults.filter((r) => r.error === undefined).length,
|
|
691
|
+
failed: mappedResults.filter((r) => r.error !== undefined).length,
|
|
443
692
|
},
|
|
444
693
|
};
|
|
445
|
-
const text = joinLines(
|
|
694
|
+
const text = joinLines(mappedResults.map((result) => {
|
|
446
695
|
if (result.error) {
|
|
447
696
|
return `${result.path}: ${result.error}`;
|
|
448
697
|
}
|
|
@@ -450,19 +699,20 @@ async function handleReadMultipleFiles(args, signal) {
|
|
|
450
699
|
}));
|
|
451
700
|
return buildToolResponse(text, structured);
|
|
452
701
|
}
|
|
453
|
-
function registerReadMultipleFilesTool(server) {
|
|
454
|
-
|
|
702
|
+
function registerReadMultipleFilesTool(server, options = {}) {
|
|
703
|
+
const handler = (args, extra) => {
|
|
455
704
|
const primaryPath = args.paths[0] ?? '';
|
|
456
705
|
return withToolDiagnostics('read_many', () => withToolErrorHandling(async () => {
|
|
457
706
|
const { signal, cleanup } = createTimedAbortSignal(extra.signal, DEFAULT_SEARCH_TIMEOUT_MS);
|
|
458
707
|
try {
|
|
459
|
-
return await handleReadMultipleFiles(args, signal);
|
|
708
|
+
return await handleReadMultipleFiles(args, signal, options.resourceStore);
|
|
460
709
|
}
|
|
461
710
|
finally {
|
|
462
711
|
cleanup();
|
|
463
712
|
}
|
|
464
713
|
}, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FILE, primaryPath)), { path: primaryPath });
|
|
465
|
-
}
|
|
714
|
+
};
|
|
715
|
+
server.registerTool('read_many', READ_MULTIPLE_FILES_TOOL, wrapToolHandler(handler, { guard: options.isInitialized }));
|
|
466
716
|
}
|
|
467
717
|
async function handleGetFileInfo(args, signal) {
|
|
468
718
|
const info = await getFileInfo(args.path, {
|
|
@@ -475,8 +725,8 @@ async function handleGetFileInfo(args, signal) {
|
|
|
475
725
|
};
|
|
476
726
|
return buildToolResponse(formatFileInfoDetails(info), structured);
|
|
477
727
|
}
|
|
478
|
-
function registerGetFileInfoTool(server) {
|
|
479
|
-
|
|
728
|
+
function registerGetFileInfoTool(server, options = {}) {
|
|
729
|
+
const handler = (args, extra) => withToolDiagnostics('stat', () => withToolErrorHandling(async () => {
|
|
480
730
|
const { signal, cleanup } = createTimedAbortSignal(extra.signal, DEFAULT_SEARCH_TIMEOUT_MS);
|
|
481
731
|
try {
|
|
482
732
|
return await handleGetFileInfo(args, signal);
|
|
@@ -484,7 +734,8 @@ function registerGetFileInfoTool(server) {
|
|
|
484
734
|
finally {
|
|
485
735
|
cleanup();
|
|
486
736
|
}
|
|
487
|
-
}, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, args.path)), { path: args.path })
|
|
737
|
+
}, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, args.path)), { path: args.path });
|
|
738
|
+
server.registerTool('stat', GET_FILE_INFO_TOOL, wrapToolHandler(handler, { guard: options.isInitialized }));
|
|
488
739
|
}
|
|
489
740
|
async function handleGetMultipleFileInfo(args, signal) {
|
|
490
741
|
const result = await getMultipleFileInfo(args.paths, {
|
|
@@ -515,8 +766,8 @@ async function handleGetMultipleFileInfo(args, signal) {
|
|
|
515
766
|
}));
|
|
516
767
|
return buildToolResponse(text, structured);
|
|
517
768
|
}
|
|
518
|
-
function registerGetMultipleFileInfoTool(server) {
|
|
519
|
-
|
|
769
|
+
function registerGetMultipleFileInfoTool(server, options = {}) {
|
|
770
|
+
const handler = (args, extra) => {
|
|
520
771
|
const primaryPath = args.paths[0] ?? '';
|
|
521
772
|
return withToolDiagnostics('stat_many', () => withToolErrorHandling(async () => {
|
|
522
773
|
const { signal, cleanup } = createTimedAbortSignal(extra.signal, DEFAULT_SEARCH_TIMEOUT_MS);
|
|
@@ -527,26 +778,75 @@ function registerGetMultipleFileInfoTool(server) {
|
|
|
527
778
|
cleanup();
|
|
528
779
|
}
|
|
529
780
|
}, (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_FOUND, primaryPath)), { path: primaryPath });
|
|
530
|
-
}
|
|
781
|
+
};
|
|
782
|
+
server.registerTool('stat_many', GET_MULTIPLE_FILE_INFO_TOOL, wrapToolHandler(handler, { guard: options.isInitialized }));
|
|
531
783
|
}
|
|
532
|
-
async function handleSearchContent(args, signal) {
|
|
784
|
+
async function handleSearchContent(args, signal, resourceStore) {
|
|
533
785
|
const basePath = resolvePathOrRoot(args.path);
|
|
534
786
|
const result = await searchContent(basePath, args.pattern, signal
|
|
535
787
|
? { includeHidden: args.includeHidden, signal }
|
|
536
788
|
: { includeHidden: args.includeHidden });
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
789
|
+
const structuredFull = buildStructuredSearchResult(result);
|
|
790
|
+
const normalizedMatches = normalizeSearchMatches(result);
|
|
791
|
+
const needsExternalize = normalizedMatches.length > MAX_INLINE_MATCHES;
|
|
792
|
+
if (!resourceStore || !needsExternalize) {
|
|
793
|
+
return buildToolResponse(buildSearchTextResult(result), structuredFull);
|
|
794
|
+
}
|
|
795
|
+
const previewMatches = normalizedMatches.slice(0, MAX_INLINE_MATCHES);
|
|
796
|
+
const previewStructured = {
|
|
797
|
+
ok: true,
|
|
798
|
+
matches: previewMatches.map((match) => ({
|
|
799
|
+
file: match.relativeFile,
|
|
800
|
+
line: match.line,
|
|
801
|
+
content: match.content,
|
|
802
|
+
matchCount: match.matchCount,
|
|
803
|
+
...(match.contextBefore
|
|
804
|
+
? { contextBefore: [...match.contextBefore] }
|
|
805
|
+
: {}),
|
|
806
|
+
...(match.contextAfter ? { contextAfter: [...match.contextAfter] } : {}),
|
|
807
|
+
})),
|
|
808
|
+
totalMatches: structuredFull.totalMatches,
|
|
809
|
+
truncated: true,
|
|
810
|
+
resourceUri: undefined,
|
|
811
|
+
};
|
|
812
|
+
const entry = resourceStore.putText({
|
|
813
|
+
name: 'grep:matches',
|
|
814
|
+
mimeType: 'application/json',
|
|
815
|
+
text: JSON.stringify(structuredFull),
|
|
816
|
+
});
|
|
817
|
+
previewStructured.resourceUri = entry.uri;
|
|
818
|
+
const text = joinLines([
|
|
819
|
+
`Found ${normalizedMatches.length} (showing first ${MAX_INLINE_MATCHES}):`,
|
|
820
|
+
...previewMatches.map((match) => {
|
|
821
|
+
const lineNum = String(match.line).padStart(4);
|
|
822
|
+
return ` ${match.relativeFile}:${lineNum}: ${match.content}`;
|
|
823
|
+
}),
|
|
824
|
+
]);
|
|
825
|
+
return buildToolResponse(text, previewStructured, [
|
|
826
|
+
buildResourceLink({
|
|
827
|
+
uri: entry.uri,
|
|
828
|
+
name: entry.name,
|
|
829
|
+
mimeType: entry.mimeType,
|
|
830
|
+
description: 'Full grep results as JSON (structuredContent)',
|
|
831
|
+
}),
|
|
832
|
+
]);
|
|
833
|
+
}
|
|
834
|
+
function registerSearchContentTool(server, options = {}) {
|
|
835
|
+
const handler = (args, extra) => withToolDiagnostics('grep', () => withToolErrorHandling(async () => handleSearchContent(args, extra.signal, options.resourceStore), (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path ?? '.')), { path: args.path ?? '.' });
|
|
836
|
+
server.registerTool('grep', SEARCH_CONTENT_TOOL, wrapToolHandler(handler, {
|
|
837
|
+
guard: options.isInitialized,
|
|
838
|
+
progressTool: 'grep',
|
|
839
|
+
}));
|
|
840
|
+
}
|
|
841
|
+
export function registerAllTools(server, options = {}) {
|
|
842
|
+
registerListAllowedDirectoriesTool(server, options);
|
|
843
|
+
registerListDirectoryTool(server, options);
|
|
844
|
+
registerSearchFilesTool(server, options);
|
|
845
|
+
registerTreeTool(server, options);
|
|
846
|
+
registerReadFileTool(server, options);
|
|
847
|
+
registerReadMultipleFilesTool(server, options);
|
|
848
|
+
registerGetFileInfoTool(server, options);
|
|
849
|
+
registerGetMultipleFileInfoTool(server, options);
|
|
850
|
+
registerSearchContentTool(server, options);
|
|
551
851
|
}
|
|
552
852
|
//# sourceMappingURL=tools.js.map
|