@@ -34,7 +34,7 @@ function expandHome(filepath: string): string {
3434}
3535
3636// Store allowed directories in normalized form
37- const allowedDirectories = args . map ( dir =>
37+ const allowedDirectories = args . map ( dir =>
3838 normalizePath ( path . resolve ( expandHome ( dir ) ) )
3939) ;
4040
@@ -58,7 +58,7 @@ async function validatePath(requestedPath: string): Promise<string> {
5858 const absolute = path . isAbsolute ( expandedPath )
5959 ? path . resolve ( expandedPath )
6060 : path . resolve ( process . cwd ( ) , expandedPath ) ;
61-
61+
6262 const normalizedRequested = normalizePath ( absolute ) ;
6363
6464 // Check if path is within allowed directories
@@ -195,7 +195,7 @@ async function searchFiles(
195195
196196 for ( const entry of entries ) {
197197 const fullPath = path . join ( currentPath , entry . name ) ;
198-
198+
199199 try {
200200 // Validate each path before processing
201201 await validatePath ( fullPath ) ;
@@ -227,7 +227,7 @@ function createUnifiedDiff(originalContent: string, newContent: string, filepath
227227 // Ensure consistent line endings for diff
228228 const normalizedOriginal = normalizeLineEndings ( originalContent ) ;
229229 const normalizedNew = normalizeLineEndings ( newContent ) ;
230-
230+
231231 return createTwoFilesPatch (
232232 filepath ,
233233 filepath ,
@@ -245,33 +245,33 @@ async function applyFileEdits(
245245) : Promise < string > {
246246 // Read file content and normalize line endings
247247 const content = normalizeLineEndings ( await fs . readFile ( filePath , 'utf-8' ) ) ;
248-
248+
249249 // Apply edits sequentially
250250 let modifiedContent = content ;
251251 for ( const edit of edits ) {
252252 const normalizedOld = normalizeLineEndings ( edit . oldText ) ;
253253 const normalizedNew = normalizeLineEndings ( edit . newText ) ;
254-
254+
255255 // If exact match exists, use it
256256 if ( modifiedContent . includes ( normalizedOld ) ) {
257257 modifiedContent = modifiedContent . replace ( normalizedOld , normalizedNew ) ;
258258 continue ;
259259 }
260-
260+
261261 // Otherwise, try line-by-line matching with flexibility for whitespace
262262 const oldLines = normalizedOld . split ( '\n' ) ;
263263 const contentLines = modifiedContent . split ( '\n' ) ;
264264 let matchFound = false ;
265-
265+
266266 for ( let i = 0 ; i <= contentLines . length - oldLines . length ; i ++ ) {
267267 const potentialMatch = contentLines . slice ( i , i + oldLines . length ) ;
268-
268+
269269 // Compare lines with normalized whitespace
270270 const isMatch = oldLines . every ( ( oldLine , j ) => {
271271 const contentLine = potentialMatch [ j ] ;
272272 return oldLine . trim ( ) === contentLine . trim ( ) ;
273273 } ) ;
274-
274+
275275 if ( isMatch ) {
276276 // Preserve original indentation of first line
277277 const originalIndent = contentLines [ i ] . match ( / ^ \s * / ) ?. [ 0 ] || '' ;
@@ -286,33 +286,33 @@ async function applyFileEdits(
286286 }
287287 return line ;
288288 } ) ;
289-
289+
290290 contentLines . splice ( i , oldLines . length , ...newLines ) ;
291291 modifiedContent = contentLines . join ( '\n' ) ;
292292 matchFound = true ;
293293 break ;
294294 }
295295 }
296-
296+
297297 if ( ! matchFound ) {
298298 throw new Error ( `Could not find exact match for edit:\n${ edit . oldText } ` ) ;
299299 }
300300 }
301-
301+
302302 // Create unified diff
303303 const diff = createUnifiedDiff ( content , modifiedContent , filePath ) ;
304-
304+
305305 // Format diff with appropriate number of backticks
306306 let numBackticks = 3 ;
307307 while ( diff . includes ( '`' . repeat ( numBackticks ) ) ) {
308308 numBackticks ++ ;
309309 }
310310 const formattedDiff = `${ '`' . repeat ( numBackticks ) } diff\n${ diff } ${ '`' . repeat ( numBackticks ) } \n\n` ;
311-
311+
312312 if ( ! dryRun ) {
313313 await fs . writeFile ( filePath , modifiedContent , 'utf-8' ) ;
314314 }
315-
315+
316316 return formattedDiff ;
317317}
318318
@@ -376,11 +376,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
376376 {
377377 name : "directory_tree" ,
378378 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." ,
384383 inputSchema : zodToJsonSchema ( DirectoryTreeArgsSchema ) as ToolInput ,
385384 } ,
386385 {
@@ -413,7 +412,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
413412 } ,
414413 {
415414 name : "list_allowed_directories" ,
416- description :
415+ description :
417416 "Returns the list of directories that this server is allowed to access. " +
418417 "Use this to understand which directories are available before trying to access files." ,
419418 inputSchema : {
@@ -518,36 +517,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
518517 }
519518
520519 case "directory_tree" : {
521- const parsed = ListDirectoryArgsSchema . safeParse ( args ) ;
520+ const parsed = DirectoryTreeArgsSchema . safeParse ( args ) ;
522521 if ( ! parsed . success ) {
523522 throw new Error ( `Invalid arguments for directory_tree: ${ parsed . error } ` ) ;
524523 }
525524
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 [ ] > {
527532 const validPath = await validatePath ( currentPath ) ;
528533 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 [ ] = [ ] ;
536535
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+ } ;
538541
539542 if ( entry . isDirectory ( ) ) {
540543 const subPath = path . join ( currentPath , entry . name ) ;
541- result + = await buildTree ( subPath , newPrefix ) ;
544+ entryData . children = await buildTree ( subPath ) ;
542545 }
546+
547+ result . push ( entryData ) ;
543548 }
544549
545550 return result ;
546551 }
547552
548- const treeOutput = await buildTree ( parsed . data . path ) ;
553+ const treeData = await buildTree ( parsed . data . path ) ;
549554 return {
550- content : [ { type : "text" , text : treeOutput } ] ,
555+ content : [ {
556+ type : "text" ,
557+ text : JSON . stringify ( treeData , null , 2 )
558+ } ] ,
551559 } ;
552560 }
553561
@@ -592,9 +600,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
592600
593601 case "list_allowed_directories" : {
594602 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' ) } `
598606 } ] ,
599607 } ;
600608 }
@@ -622,4 +630,4 @@ async function runServer() {
622630runServer ( ) . catch ( ( error ) => {
623631 console . error ( "Fatal error running server:" , error ) ;
624632 process . exit ( 1 ) ;
625- } ) ;
633+ } ) ;
0 commit comments