@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.
Files changed (78) hide show
  1. package/README.md +60 -12
  2. package/dist/config.d.ts +1 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/instructions.md +27 -118
  6. package/dist/lib/constants.d.ts +1 -0
  7. package/dist/lib/constants.d.ts.map +1 -1
  8. package/dist/lib/constants.js +1 -0
  9. package/dist/lib/constants.js.map +1 -1
  10. package/dist/lib/file-operations/file-info.d.ts.map +1 -1
  11. package/dist/lib/file-operations/file-info.js +2 -0
  12. package/dist/lib/file-operations/file-info.js.map +1 -1
  13. package/dist/lib/file-operations/gitignore.d.ts +6 -0
  14. package/dist/lib/file-operations/gitignore.d.ts.map +1 -0
  15. package/dist/lib/file-operations/gitignore.js +42 -0
  16. package/dist/lib/file-operations/gitignore.js.map +1 -0
  17. package/dist/lib/file-operations/read-multiple-files.d.ts +5 -1
  18. package/dist/lib/file-operations/read-multiple-files.d.ts.map +1 -1
  19. package/dist/lib/file-operations/read-multiple-files.js +48 -17
  20. package/dist/lib/file-operations/read-multiple-files.js.map +1 -1
  21. package/dist/lib/file-operations/search-content-scan.d.ts +29 -0
  22. package/dist/lib/file-operations/search-content-scan.d.ts.map +1 -0
  23. package/dist/lib/file-operations/search-content-scan.js +468 -0
  24. package/dist/lib/file-operations/search-content-scan.js.map +1 -0
  25. package/dist/lib/file-operations/search-content-worker-api.d.ts +56 -0
  26. package/dist/lib/file-operations/search-content-worker-api.d.ts.map +1 -0
  27. package/dist/lib/file-operations/search-content-worker-api.js +239 -0
  28. package/dist/lib/file-operations/search-content-worker-api.js.map +1 -0
  29. package/dist/lib/file-operations/search-content.d.ts.map +1 -1
  30. package/dist/lib/file-operations/search-content.js +59 -53
  31. package/dist/lib/file-operations/search-content.js.map +1 -1
  32. package/dist/lib/file-operations/search-files.d.ts +1 -0
  33. package/dist/lib/file-operations/search-files.d.ts.map +1 -1
  34. package/dist/lib/file-operations/search-files.js +11 -2
  35. package/dist/lib/file-operations/search-files.js.map +1 -1
  36. package/dist/lib/file-operations/tree.d.ts +25 -0
  37. package/dist/lib/file-operations/tree.d.ts.map +1 -0
  38. package/dist/lib/file-operations/tree.js +172 -0
  39. package/dist/lib/file-operations/tree.js.map +1 -0
  40. package/dist/lib/fs-helpers.d.ts +5 -1
  41. package/dist/lib/fs-helpers.d.ts.map +1 -1
  42. package/dist/lib/fs-helpers.js +131 -14
  43. package/dist/lib/fs-helpers.js.map +1 -1
  44. package/dist/lib/path-validation.d.ts.map +1 -1
  45. package/dist/lib/path-validation.js +15 -17
  46. package/dist/lib/path-validation.js.map +1 -1
  47. package/dist/lib/resource-store.d.ts +23 -0
  48. package/dist/lib/resource-store.d.ts.map +1 -0
  49. package/dist/lib/resource-store.js +75 -0
  50. package/dist/lib/resource-store.js.map +1 -0
  51. package/dist/lib/strict-stdio-transport.d.ts +31 -0
  52. package/dist/lib/strict-stdio-transport.d.ts.map +1 -0
  53. package/dist/lib/strict-stdio-transport.js +158 -0
  54. package/dist/lib/strict-stdio-transport.js.map +1 -0
  55. package/dist/resources.d.ts +5 -0
  56. package/dist/resources.d.ts.map +1 -0
  57. package/dist/resources.js +43 -0
  58. package/dist/resources.js.map +1 -0
  59. package/dist/schemas.d.ts +78 -12
  60. package/dist/schemas.d.ts.map +1 -1
  61. package/dist/schemas.js +125 -24
  62. package/dist/schemas.js.map +1 -1
  63. package/dist/server.d.ts.map +1 -1
  64. package/dist/server.js +52 -11
  65. package/dist/server.js.map +1 -1
  66. package/dist/tooling/tool-response.d.ts +32 -0
  67. package/dist/tooling/tool-response.d.ts.map +1 -0
  68. package/dist/tooling/tool-response.js +63 -0
  69. package/dist/tooling/tool-response.js.map +1 -0
  70. package/dist/tooling/tools-registry.d.ts +5 -0
  71. package/dist/tooling/tools-registry.d.ts.map +1 -0
  72. package/dist/tooling/tools-registry.js +532 -0
  73. package/dist/tooling/tools-registry.js.map +1 -0
  74. package/dist/tools.d.ts +12 -12
  75. package/dist/tools.d.ts.map +1 -1
  76. package/dist/tools.js +351 -51
  77. package/dist/tools.js.map +1 -1
  78. 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
- function buildContentBlock(text, structuredContent) {
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
- server.registerTool('ls', LIST_DIRECTORY_TOOL, (args, extra) => withToolDiagnostics('ls', () => withToolErrorHandling(() => handleListDirectory(args, extra.signal), (error) => buildToolErrorResponse(error, ErrorCode.E_NOT_DIRECTORY, args.path ?? '.')), { path: args.path ?? '.' }));
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
- const text = joinLines([
373
- `Found ${relativeResults.length}:`,
374
- ...relativeResults.map((entry) => ` ${entry.path}`),
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
- server.registerTool('find', SEARCH_FILES_TOOL, (args, extra) => withToolDiagnostics('find', () => withToolErrorHandling(async () => {
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
- async function handleReadFile(args, signal) {
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
- return buildToolResponse(result.content, structured);
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: results.map((result) => ({
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: results.length,
441
- succeeded: results.filter((r) => r.error === undefined).length,
442
- failed: results.filter((r) => r.error !== undefined).length,
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(results.map((result) => {
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
- server.registerTool('read_many', READ_MULTIPLE_FILES_TOOL, (args, extra) => {
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
- server.registerTool('stat', GET_FILE_INFO_TOOL, (args, extra) => withToolDiagnostics('stat', () => withToolErrorHandling(async () => {
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
- server.registerTool('stat_many', GET_MULTIPLE_FILE_INFO_TOOL, (args, extra) => {
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
- return buildToolResponse(buildSearchTextResult(result), buildStructuredSearchResult(result));
538
- }
539
- function registerSearchContentTool(server) {
540
- server.registerTool('grep', SEARCH_CONTENT_TOOL, (args, extra) => withToolDiagnostics('grep', () => withToolErrorHandling(async () => handleSearchContent(args, extra.signal), (error) => buildToolErrorResponse(error, ErrorCode.E_UNKNOWN, args.path ?? '.')), { path: args.path ?? '.' }));
541
- }
542
- export function registerAllTools(server) {
543
- registerListAllowedDirectoriesTool(server);
544
- registerListDirectoryTool(server);
545
- registerSearchFilesTool(server);
546
- registerReadFileTool(server);
547
- registerReadMultipleFilesTool(server);
548
- registerGetFileInfoTool(server);
549
- registerGetMultipleFileInfoTool(server);
550
- registerSearchContentTool(server);
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