Skip to content

Commit ba20bd6

Browse files
committed
Update the way the stream is concatenated
Update the ts sdk
1 parent 2feb7cb commit ba20bd6

3 files changed

Lines changed: 47 additions & 43 deletions

File tree

package-lock.json

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/filesystem/index.ts

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -379,10 +379,10 @@ async function applyFileEdits(
379379
function 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.
482482
async 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
}

src/filesystem/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"test": "jest --config=jest.config.cjs --coverage"
2121
},
2222
"dependencies": {
23-
"@modelcontextprotocol/sdk": "^1.12.3",
23+
"@modelcontextprotocol/sdk": "^1.16.0",
2424
"diff": "^5.1.0",
2525
"glob": "^10.3.10",
2626
"minimatch": "^10.0.1",
@@ -38,4 +38,4 @@
3838
"ts-node": "^10.9.2",
3939
"typescript": "^5.8.2"
4040
}
41-
}
41+
}

0 commit comments

Comments
 (0)