Skip to content

File write read improvements #150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 7, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/handlers/filesystem-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,9 @@ export async function handleWriteFile(args: unknown): Promise<ServerResult> {
const lineCount = lines.length;
let errorMessage = "";
if (lineCount > MAX_LINES) {
errorMessage = `File was written with warning: Line count limit exceeded: ${lineCount} lines (maximum: ${MAX_LINES}).
errorMessage = `File written successfully! (${lineCount} lines)

SOLUTION: Split your content into smaller chunks:
1. First chunk: write_file(path, firstChunk, {mode: 'rewrite'})
2. Additional chunks: write_file(path, nextChunk, {mode: 'append'})`;
💡 Performance tip: For optimal speed, consider chunking files into ≤30 line pieces in future operations.`;
}

// Pass the mode parameter to writeFile
Expand Down
63 changes: 39 additions & 24 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {

Supports partial file reading with:
- 'offset' (start line, default: 0)
* Positive: Start from line N (0-based indexing)
* Negative: Read last N lines from end (tail behavior)
- 'length' (max lines to read, default: configurable via 'fileReadLineLimit' setting, initially 1000)
* Used with positive offsets for range reading
* Ignored when offset is negative (reads all requested tail lines)

Examples:
- offset: 0, length: 10 → First 10 lines
- offset: 100, length: 5 → Lines 100-104
- offset: -20 → Last 20 lines
- offset: -5, length: 10 → Last 5 lines (length ignored)

Performance optimizations:
- Large files with negative offsets use reverse reading for efficiency
- Large files with deep positive offsets use byte estimation
- Small files use fast readline streaming

When reading from the file system, only works within allowed directories.
Can fetch content from URLs when isUrl parameter is set to true
Expand Down Expand Up @@ -158,30 +173,30 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
{
name: "write_file",
description: `
Write or append to file contents with a configurable line limit per call (default: 50 lines).
THIS IS A STRICT REQUIREMENT. ANY file with more than the configured limit MUST BE written in chunks or IT WILL FAIL.

⚠️ IMPORTANT: PREVENTATIVE CHUNKING REQUIRED in these scenarios:
1. When content exceeds 2,000 words or 30 lines
2. When writing MULTIPLE files one after another (each next file is more likely to be truncated)
3. When the file is the LAST ONE in a series of operations in the same message

ALWAYS split files writes in to multiple smaller writes PREEMPTIVELY without asking the user in these scenarios.
REQUIRED PROCESS FOR LARGE NEW FILE WRITES OR REWRITES:
1. FIRST → write_file(filePath, firstChunk, {mode: 'rewrite'})
2. THEN → write_file(filePath, secondChunk, {mode: 'append'})
3. THEN → write_file(filePath, thirdChunk, {mode: 'append'})
... and so on for each chunk

HANDLING TRUNCATION ("Continue" prompts):
If user asked to "Continue" after unfinished file write:
1. First, read the file to find out what content was successfully written
2. Identify exactly where the content was truncated
3. Continue writing ONLY the remaining content using {mode: 'append'}
4. Split the remaining content into smaller chunks (15-20 lines per chunk)

Files over the line limit (configurable via 'fileWriteLineLimit' setting) WILL BE REJECTED if not broken into chunks as described above.
Write or append to file contents.

🎯 CHUNKING IS STANDARD PRACTICE: Always write files in chunks of 25-30 lines maximum.
This is the normal, recommended way to write files - not an emergency measure.

STANDARD PROCESS FOR ANY FILE:
1. FIRST → write_file(filePath, firstChunk, {mode: 'rewrite'}) [≤30 lines]
2. THEN → write_file(filePath, secondChunk, {mode: 'append'}) [≤30 lines]
3. CONTINUE → write_file(filePath, nextChunk, {mode: 'append'}) [≤30 lines]

⚠️ ALWAYS CHUNK PROACTIVELY - don't wait for performance warnings!

WHEN TO CHUNK (always be proactive):
1. Any file expected to be longer than 25-30 lines
2. When writing multiple files in sequence
3. When creating documentation, code files, or configuration files

HANDLING CONTINUATION ("Continue" prompts):
If user asks to "Continue" after an incomplete operation:
1. Read the file to see what was successfully written
2. Continue writing ONLY the remaining content using {mode: 'append'}
3. Keep chunks to 25-30 lines each

Files over 50 lines will generate performance notes but are still written successfully.
Only works within allowed directories.

${PATH_GUIDANCE}
Expand Down
6 changes: 3 additions & 3 deletions src/tools/edit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readFile, writeFile } from './filesystem.js';
import { readFile, writeFile, readFileInternal } from './filesystem.js';
import { ServerResult } from '../types.js';
import { recursiveFuzzyIndexOf, getSimilarityRatio } from './fuzzySearch.js';
import { capture } from '../utils/capture.js';
Expand Down Expand Up @@ -119,8 +119,8 @@ export async function performSearchReplace(filePath: string, block: SearchReplac
}


// Read file as plain string
const {content} = await readFile(filePath, false, 0, Number.MAX_SAFE_INTEGER);
// Read file as plain string without status messages
const content = await readFileInternal(filePath, 0, Number.MAX_SAFE_INTEGER);

// Make sure content is a string
if (typeof content !== 'string') {
Expand Down
Loading