@@ -34,7 +34,7 @@ function expandHome(filepath: string): string {
34
34
}
35
35
36
36
// Store allowed directories in normalized form
37
- const allowedDirectories = args . map ( dir =>
37
+ const allowedDirectories = args . map ( dir =>
38
38
normalizePath ( path . resolve ( expandHome ( dir ) ) )
39
39
) ;
40
40
@@ -58,7 +58,7 @@ async function validatePath(requestedPath: string): Promise<string> {
58
58
const absolute = path . isAbsolute ( expandedPath )
59
59
? path . resolve ( expandedPath )
60
60
: path . resolve ( process . cwd ( ) , expandedPath ) ;
61
-
61
+
62
62
const normalizedRequested = normalizePath ( absolute ) ;
63
63
64
64
// Check if path is within allowed directories
@@ -195,7 +195,7 @@ async function searchFiles(
195
195
196
196
for ( const entry of entries ) {
197
197
const fullPath = path . join ( currentPath , entry . name ) ;
198
-
198
+
199
199
try {
200
200
// Validate each path before processing
201
201
await validatePath ( fullPath ) ;
@@ -227,7 +227,7 @@ function createUnifiedDiff(originalContent: string, newContent: string, filepath
227
227
// Ensure consistent line endings for diff
228
228
const normalizedOriginal = normalizeLineEndings ( originalContent ) ;
229
229
const normalizedNew = normalizeLineEndings ( newContent ) ;
230
-
230
+
231
231
return createTwoFilesPatch (
232
232
filepath ,
233
233
filepath ,
@@ -245,33 +245,33 @@ async function applyFileEdits(
245
245
) : Promise < string > {
246
246
// Read file content and normalize line endings
247
247
const content = normalizeLineEndings ( await fs . readFile ( filePath , 'utf-8' ) ) ;
248
-
248
+
249
249
// Apply edits sequentially
250
250
let modifiedContent = content ;
251
251
for ( const edit of edits ) {
252
252
const normalizedOld = normalizeLineEndings ( edit . oldText ) ;
253
253
const normalizedNew = normalizeLineEndings ( edit . newText ) ;
254
-
254
+
255
255
// If exact match exists, use it
256
256
if ( modifiedContent . includes ( normalizedOld ) ) {
257
257
modifiedContent = modifiedContent . replace ( normalizedOld , normalizedNew ) ;
258
258
continue ;
259
259
}
260
-
260
+
261
261
// Otherwise, try line-by-line matching with flexibility for whitespace
262
262
const oldLines = normalizedOld . split ( '\n' ) ;
263
263
const contentLines = modifiedContent . split ( '\n' ) ;
264
264
let matchFound = false ;
265
-
265
+
266
266
for ( let i = 0 ; i <= contentLines . length - oldLines . length ; i ++ ) {
267
267
const potentialMatch = contentLines . slice ( i , i + oldLines . length ) ;
268
-
268
+
269
269
// Compare lines with normalized whitespace
270
270
const isMatch = oldLines . every ( ( oldLine , j ) => {
271
271
const contentLine = potentialMatch [ j ] ;
272
272
return oldLine . trim ( ) === contentLine . trim ( ) ;
273
273
} ) ;
274
-
274
+
275
275
if ( isMatch ) {
276
276
// Preserve original indentation of first line
277
277
const originalIndent = contentLines [ i ] . match ( / ^ \s * / ) ?. [ 0 ] || '' ;
@@ -286,33 +286,33 @@ async function applyFileEdits(
286
286
}
287
287
return line ;
288
288
} ) ;
289
-
289
+
290
290
contentLines . splice ( i , oldLines . length , ...newLines ) ;
291
291
modifiedContent = contentLines . join ( '\n' ) ;
292
292
matchFound = true ;
293
293
break ;
294
294
}
295
295
}
296
-
296
+
297
297
if ( ! matchFound ) {
298
298
throw new Error ( `Could not find exact match for edit:\n${ edit . oldText } ` ) ;
299
299
}
300
300
}
301
-
301
+
302
302
// Create unified diff
303
303
const diff = createUnifiedDiff ( content , modifiedContent , filePath ) ;
304
-
304
+
305
305
// Format diff with appropriate number of backticks
306
306
let numBackticks = 3 ;
307
307
while ( diff . includes ( '`' . repeat ( numBackticks ) ) ) {
308
308
numBackticks ++ ;
309
309
}
310
310
const formattedDiff = `${ '`' . repeat ( numBackticks ) } diff\n${ diff } ${ '`' . repeat ( numBackticks ) } \n\n` ;
311
-
311
+
312
312
if ( ! dryRun ) {
313
313
await fs . writeFile ( filePath , modifiedContent , 'utf-8' ) ;
314
314
}
315
-
315
+
316
316
return formattedDiff ;
317
317
}
318
318
@@ -376,11 +376,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
376
376
{
377
377
name : "directory_tree" ,
378
378
description :
379
- "Get a recursive tree view of files and directories starting from a specified path. " +
380
- "Results are formatted in a hierarchical ASCII tree structure with proper indentation " +
381
- "using pipes and dashes (│ ├ └ ─). Files and directories are distinguished " +
382
- "with [F] and [D] prefixes. This tool provides a comprehensive visualization of nested " +
383
- "directory structures. Only works within allowed directories." ,
379
+ "Get a recursive tree view of files and directories as a JSON structure. " +
380
+ "Each entry includes 'name', 'type' (file/directory), and 'children' for directories. " +
381
+ "Files have no children array, while directories always have a children array (which may be empty). " +
382
+ "The output is formatted with 2-space indentation for readability. Only works within allowed directories." ,
384
383
inputSchema : zodToJsonSchema ( DirectoryTreeArgsSchema ) as ToolInput ,
385
384
} ,
386
385
{
@@ -413,7 +412,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
413
412
} ,
414
413
{
415
414
name : "list_allowed_directories" ,
416
- description :
415
+ description :
417
416
"Returns the list of directories that this server is allowed to access. " +
418
417
"Use this to understand which directories are available before trying to access files." ,
419
418
inputSchema : {
@@ -518,36 +517,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
518
517
}
519
518
520
519
case "directory_tree" : {
521
- const parsed = ListDirectoryArgsSchema . safeParse ( args ) ;
520
+ const parsed = DirectoryTreeArgsSchema . safeParse ( args ) ;
522
521
if ( ! parsed . success ) {
523
522
throw new Error ( `Invalid arguments for directory_tree: ${ parsed . error } ` ) ;
524
523
}
525
524
526
- async function buildTree ( currentPath : string , prefix = "" ) : Promise < string > {
525
+ interface TreeEntry {
526
+ name: string ;
527
+ type: 'file' | 'directory' ;
528
+ children ?: TreeEntry [ ] ;
529
+ }
530
+
531
+ async function buildTree ( currentPath : string ) : Promise < TreeEntry [ ] > {
527
532
const validPath = await validatePath ( currentPath ) ;
528
533
const entries = await fs . readdir ( validPath , { withFileTypes : true } ) ;
529
- let result = "" ;
530
-
531
- for ( let i = 0 ; i < entries . length ; i ++ ) {
532
- const entry = entries [ i ] ;
533
- const isLast = i === entries . length - 1 ;
534
- const connector = isLast ? "└── " : "├── " ;
535
- const newPrefix = prefix + ( isLast ? " " : "│ " ) ;
534
+ const result : TreeEntry [ ] = [ ] ;
536
535
537
- result += `${ prefix } ${ connector } ${ entry . isDirectory ( ) ? "[D]" : "[F]" } ${ entry . name } \n` ;
536
+ for ( const entry of entries ) {
537
+ const entryData : TreeEntry = {
538
+ name : entry . name ,
539
+ type : entry . isDirectory ( ) ? 'directory' : 'file'
540
+ } ;
538
541
539
542
if ( entry . isDirectory ( ) ) {
540
543
const subPath = path . join ( currentPath , entry . name ) ;
541
- result + = await buildTree ( subPath , newPrefix ) ;
544
+ entryData . children = await buildTree ( subPath ) ;
542
545
}
546
+
547
+ result . push ( entryData ) ;
543
548
}
544
549
545
550
return result ;
546
551
}
547
552
548
- const treeOutput = await buildTree ( parsed . data . path ) ;
553
+ const treeData = await buildTree ( parsed . data . path ) ;
549
554
return {
550
- content : [ { type : "text" , text : treeOutput } ] ,
555
+ content : [ {
556
+ type : "text" ,
557
+ text : JSON . stringify ( treeData , null , 2 )
558
+ } ] ,
551
559
} ;
552
560
}
553
561
@@ -592,9 +600,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
592
600
593
601
case "list_allowed_directories" : {
594
602
return {
595
- content : [ {
596
- type : "text" ,
597
- text : `Allowed directories:\n${ allowedDirectories . join ( '\n' ) } `
603
+ content : [ {
604
+ type : "text" ,
605
+ text : `Allowed directories:\n${ allowedDirectories . join ( '\n' ) } `
598
606
} ] ,
599
607
} ;
600
608
}
@@ -622,4 +630,4 @@ async function runServer() {
622
630
runServer ( ) . catch ( ( error ) => {
623
631
console . error ( "Fatal error running server:" , error ) ;
624
632
process . exit ( 1 ) ;
625
- } ) ;
633
+ } ) ;
0 commit comments