@@ -379,10 +379,10 @@ async function applyFileEdits(
379379function formatSize ( bytes : number ) : string {
380380 const units = [ 'B' , 'KB' , 'MB' , 'GB' , 'TB' ] ;
381381 if ( bytes === 0 ) return '0 B' ;
382-
382+
383383 const i = Math . floor ( Math . log ( bytes ) / Math . log ( 1024 ) ) ;
384384 if ( i === 0 ) return `${ bytes } ${ units [ i ] } ` ;
385-
385+
386386 return `${ ( bytes / Math . pow ( 1024 , i ) ) . toFixed ( 2 ) } ${ units [ i ] } ` ;
387387}
388388
@@ -391,9 +391,9 @@ async function tailFile(filePath: string, numLines: number): Promise<string> {
391391 const CHUNK_SIZE = 1024 ; // Read 1KB at a time
392392 const stats = await fs . stat ( filePath ) ;
393393 const fileSize = stats . size ;
394-
394+
395395 if ( fileSize === 0 ) return '' ;
396-
396+
397397 // Open file for reading
398398 const fileHandle = await fs . open ( filePath , 'r' ) ;
399399 try {
@@ -402,36 +402,36 @@ async function tailFile(filePath: string, numLines: number): Promise<string> {
402402 let chunk = Buffer . alloc ( CHUNK_SIZE ) ;
403403 let linesFound = 0 ;
404404 let remainingText = '' ;
405-
405+
406406 // Read chunks from the end of the file until we have enough lines
407407 while ( position > 0 && linesFound < numLines ) {
408408 const size = Math . min ( CHUNK_SIZE , position ) ;
409409 position -= size ;
410-
410+
411411 const { bytesRead } = await fileHandle . read ( chunk , 0 , size , position ) ;
412412 if ( ! bytesRead ) break ;
413-
413+
414414 // Get the chunk as a string and prepend any remaining text from previous iteration
415415 const readData = chunk . slice ( 0 , bytesRead ) . toString ( 'utf-8' ) ;
416416 const chunkText = readData + remainingText ;
417-
417+
418418 // Split by newlines and count
419419 const chunkLines = normalizeLineEndings ( chunkText ) . split ( '\n' ) ;
420-
420+
421421 // If this isn't the end of the file, the first line is likely incomplete
422422 // Save it to prepend to the next chunk
423423 if ( position > 0 ) {
424424 remainingText = chunkLines [ 0 ] ;
425425 chunkLines . shift ( ) ; // Remove the first (incomplete) line
426426 }
427-
427+
428428 // Add lines to our result (up to the number we need)
429429 for ( let i = chunkLines . length - 1 ; i >= 0 && linesFound < numLines ; i -- ) {
430430 lines . unshift ( chunkLines [ i ] ) ;
431431 linesFound ++ ;
432432 }
433433 }
434-
434+
435435 return lines . join ( '\n' ) ;
436436 } finally {
437437 await fileHandle . close ( ) ;
@@ -446,14 +446,14 @@ async function headFile(filePath: string, numLines: number): Promise<string> {
446446 let buffer = '' ;
447447 let bytesRead = 0 ;
448448 const chunk = Buffer . alloc ( 1024 ) ; // 1KB buffer
449-
449+
450450 // Read chunks and count lines until we have enough or reach EOF
451451 while ( lines . length < numLines ) {
452452 const result = await fileHandle . read ( chunk , 0 , chunk . length , bytesRead ) ;
453453 if ( result . bytesRead === 0 ) break ; // End of file
454454 bytesRead += result . bytesRead ;
455455 buffer += chunk . slice ( 0 , result . bytesRead ) . toString ( 'utf-8' ) ;
456-
456+
457457 const newLineIndex = buffer . lastIndexOf ( '\n' ) ;
458458 if ( newLineIndex !== - 1 ) {
459459 const completeLines = buffer . slice ( 0 , newLineIndex ) . split ( '\n' ) ;
@@ -464,29 +464,32 @@ async function headFile(filePath: string, numLines: number): Promise<string> {
464464 }
465465 }
466466 }
467-
467+
468468 // If there is leftover content and we still need lines, add it
469469 if ( buffer . length > 0 && lines . length < numLines ) {
470470 lines . push ( buffer ) ;
471471 }
472-
472+
473473 return lines . join ( '\n' ) ;
474474 } finally {
475475 await fileHandle . close ( ) ;
476476 }
477477}
478478
479- // Stream a file and return its Base64 representation without loading the
480- // entire file into memory at once. Chunks are encoded individually and
481- // concatenated into the final string .
479+ // Reads a file as a stream of buffers, concatenates them, and then encodes
480+ // the result to a Base64 string. This is a memory-efficient way to handle
481+ // binary data from a stream before the final encoding .
482482async function readFileAsBase64Stream ( filePath : string ) : Promise < string > {
483483 return new Promise ( ( resolve , reject ) => {
484- const stream = createReadStream ( filePath , { encoding : 'base64' } ) ;
485- let data = '' ;
484+ const stream = createReadStream ( filePath ) ;
485+ const chunks : Buffer [ ] = [ ] ;
486486 stream . on ( 'data' , ( chunk ) => {
487- data += chunk ;
487+ chunks . push ( chunk as Buffer ) ;
488+ } ) ;
489+ stream . on ( 'end' , ( ) => {
490+ const finalBuffer = Buffer . concat ( chunks ) ;
491+ resolve ( finalBuffer . toString ( 'base64' ) ) ;
488492 } ) ;
489- stream . on ( 'end' , ( ) => resolve ( data ) ) ;
490493 stream . on ( 'error' , ( err ) => reject ( err ) ) ;
491494 } ) ;
492495}
@@ -631,27 +634,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
631634 throw new Error ( `Invalid arguments for read_text_file: ${ parsed . error } ` ) ;
632635 }
633636 const validPath = await validatePath ( parsed . data . path ) ;
634-
637+
635638 if ( parsed . data . head && parsed . data . tail ) {
636639 throw new Error ( "Cannot specify both head and tail parameters simultaneously" ) ;
637640 }
638-
641+
639642 if ( parsed . data . tail ) {
640643 // Use memory-efficient tail implementation for large files
641644 const tailContent = await tailFile ( validPath , parsed . data . tail ) ;
642645 return {
643646 content : [ { type : "text" , text : tailContent } ] ,
644647 } ;
645648 }
646-
649+
647650 if ( parsed . data . head ) {
648651 // Use memory-efficient head implementation for large files
649652 const headContent = await headFile ( validPath , parsed . data . head ) ;
650653 return {
651654 content : [ { type : "text" , text : headContent } ] ,
652655 } ;
653656 }
654-
657+
655658 const content = await fs . readFile ( validPath , "utf-8" ) ;
656659 return {
657660 content : [ { type : "text" , text : content } ] ,
@@ -686,7 +689,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
686689 ? "audio"
687690 : "blob" ;
688691 return {
689- content : [ { type, data, mimeType } as any ] ,
692+ content : [ { type, data, mimeType } ] ,
690693 } ;
691694 }
692695
@@ -794,7 +797,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
794797 }
795798 const validPath = await validatePath ( parsed . data . path ) ;
796799 const entries = await fs . readdir ( validPath , { withFileTypes : true } ) ;
797-
800+
798801 // Get detailed information for each entry
799802 const detailedEntries = await Promise . all (
800803 entries . map ( async ( entry ) => {
@@ -817,7 +820,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
817820 }
818821 } )
819822 ) ;
820-
823+
821824 // Sort entries based on sortBy parameter
822825 const sortedEntries = [ ...detailedEntries ] . sort ( ( a , b ) => {
823826 if ( parsed . data . sortBy === 'size' ) {
@@ -826,29 +829,29 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
826829 // Default sort by name
827830 return a . name . localeCompare ( b . name ) ;
828831 } ) ;
829-
832+
830833 // Format the output
831- const formattedEntries = sortedEntries . map ( entry =>
834+ const formattedEntries = sortedEntries . map ( entry =>
832835 `${ entry . isDirectory ? "[DIR]" : "[FILE]" } ${ entry . name . padEnd ( 30 ) } ${
833836 entry . isDirectory ? "" : formatSize ( entry . size ) . padStart ( 10 )
834837 } `
835838 ) ;
836-
839+
837840 // Add summary
838841 const totalFiles = detailedEntries . filter ( e => ! e . isDirectory ) . length ;
839842 const totalDirs = detailedEntries . filter ( e => e . isDirectory ) . length ;
840843 const totalSize = detailedEntries . reduce ( ( sum , entry ) => sum + ( entry . isDirectory ? 0 : entry . size ) , 0 ) ;
841-
844+
842845 const summary = [
843846 "" ,
844847 `Total: ${ totalFiles } files, ${ totalDirs } directories` ,
845848 `Combined size: ${ formatSize ( totalSize ) } `
846849 ] ;
847-
850+
848851 return {
849- content : [ {
850- type : "text" ,
851- text : [ ...formattedEntries , ...summary ] . join ( "\n" )
852+ content : [ {
853+ type : "text" ,
854+ text : [ ...formattedEntries , ...summary ] . join ( "\n" )
852855 } ] ,
853856 } ;
854857 }
0 commit comments