fileditor-mcp 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +128 -128
- package/docs/MCP-INTERFACE.cn.md +1061 -975
- package/docs/MCP-INTERFACE.en.md +974 -881
- package/docs/README.cn.md +128 -128
- package/package.json +30 -26
- package/src/handlers/applyDiff.js +324 -298
- package/src/handlers/insertContent.js +153 -179
- package/src/handlers/listFiles.js +54 -65
- package/src/handlers/readFile.js +109 -109
- package/src/handlers/searchAndReplace.js +217 -198
- package/src/handlers/setWorkspace.js +26 -26
- package/src/handlers/writeFile.js +91 -97
- package/src/index.js +22 -20
- package/src/server.js +85 -85
- package/src/tools/toolDefinitions.js +301 -291
- package/src/utils/fileUtils.js +716 -238
- package/test/ApplyDiffHandler.test.js +754 -754
- package/test/InsertContentHandler.test.js +369 -371
- package/test/ListFilesHandler.test.js +300 -302
- package/test/ReadFileHandler.test.js +213 -213
- package/test/SearchAndReplaceHandler.test.js +505 -505
- package/test/SetWorkspaceHandler.test.js +290 -290
- package/test/WriteFileHandler.test.js +289 -289
- package/test/runAllTests.js +233 -233
|
@@ -1,179 +1,153 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
* @param {string}
|
|
35
|
-
* @
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
positionDesc =
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Build return message
|
|
157
|
-
let responseText = '';
|
|
158
|
-
|
|
159
|
-
if (results.length > 0) {
|
|
160
|
-
responseText += `Successfully processed ${results.length} file(s):\n\n`;
|
|
161
|
-
for (const result of results) {
|
|
162
|
-
responseText += `✅ ${result.file}: ${result.message}\n`;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (errors.length > 0) {
|
|
167
|
-
responseText += `\nErrors encountered:\n`;
|
|
168
|
-
for (const error of errors) {
|
|
169
|
-
responseText += `❌ ${error.file}: ${error.error}\n`;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (results.length === 0 && errors.length > 0) {
|
|
174
|
-
throw new Error(`Failed to insert content in any files: ${errors.map(e => e.error).join(', ')}`);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return FileUtils.createResponse(responseText.trim());
|
|
178
|
-
}
|
|
179
|
-
}
|
|
1
|
+
import { FileUtils } from '../utils/fileUtils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* insert_content tool handler
|
|
5
|
+
*/
|
|
6
|
+
export class InsertContentHandler {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handle insert_content request
|
|
10
|
+
* @param {Object} args - Request parameters
|
|
11
|
+
* @returns {Promise<Object>} Response result
|
|
12
|
+
*/
|
|
13
|
+
static async handle(args) {
|
|
14
|
+
const { path, line, content } = args;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// Check if single file or multiple files
|
|
18
|
+
if (Array.isArray(path)) {
|
|
19
|
+
// Insert into multiple files
|
|
20
|
+
return await InsertContentHandler.insertContentMultipleFiles(args);
|
|
21
|
+
} else {
|
|
22
|
+
// Insert into single file
|
|
23
|
+
return await InsertContentHandler.insertContentSingleFile(path, line, content);
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`Failed to insert content: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Insert content into a single file
|
|
32
|
+
* @param {string} filePath - File path
|
|
33
|
+
* @param {number} line - Line number
|
|
34
|
+
* @param {string} content - Content
|
|
35
|
+
* @returns {Promise<Object>} Response result
|
|
36
|
+
*/
|
|
37
|
+
static async insertContentSingleFile(filePath, line, content) {
|
|
38
|
+
// Check if file exists
|
|
39
|
+
if (!FileUtils.fileExists(filePath)) {
|
|
40
|
+
throw new Error(`File not found: ${filePath}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Read existing content
|
|
44
|
+
const existingContent = await FileUtils.readFile(filePath);
|
|
45
|
+
const lines = existingContent.split('\n');
|
|
46
|
+
|
|
47
|
+
// Handle insert position
|
|
48
|
+
let insertPosition;
|
|
49
|
+
if (line === 0) {
|
|
50
|
+
// 0 means end of file
|
|
51
|
+
insertPosition = lines.length;
|
|
52
|
+
} else if (line > 0) {
|
|
53
|
+
// Positive: 1-based index
|
|
54
|
+
insertPosition = line - 1; // Convert to 0-based index
|
|
55
|
+
|
|
56
|
+
// Check if insert position is valid
|
|
57
|
+
if (insertPosition > lines.length) {
|
|
58
|
+
throw new Error(`Line number ${line} exceeds file length (${lines.length} lines)`);
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Negative: calculate insert position from end
|
|
62
|
+
// -1 means before last line, -2 means before second last line, etc.
|
|
63
|
+
insertPosition = lines.length + line;
|
|
64
|
+
|
|
65
|
+
if (insertPosition < 0) {
|
|
66
|
+
throw new Error(`Negative line number ${line} is out of range for file with ${lines.length} lines`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Handle content to insert (may contain multiple lines)
|
|
71
|
+
const newLines = content.split('\n');
|
|
72
|
+
|
|
73
|
+
// Insert new content (both negative and positive are insert operations)
|
|
74
|
+
lines.splice(insertPosition, 0, ...newLines);
|
|
75
|
+
|
|
76
|
+
// Write back to file
|
|
77
|
+
await FileUtils.writeFile(filePath, lines.join('\n'));
|
|
78
|
+
|
|
79
|
+
// Build response message
|
|
80
|
+
let positionDesc;
|
|
81
|
+
if (line === 0) {
|
|
82
|
+
positionDesc = 'end of file';
|
|
83
|
+
} else if (line > 0) {
|
|
84
|
+
positionDesc = `line ${line}`;
|
|
85
|
+
} else {
|
|
86
|
+
positionDesc = `line ${Math.abs(line)} from end (before current line ${insertPosition + 1})`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const message = `Successfully inserted ${newLines.length} line(s) at ${positionDesc} in ${filePath}. File now has ${lines.length} lines.`;
|
|
90
|
+
return FileUtils.createResponse(message);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Insert content into multiple files
|
|
95
|
+
* @param {Object} args - Original arguments object
|
|
96
|
+
* @returns {Promise<Object>} Response result
|
|
97
|
+
*/
|
|
98
|
+
static async insertContentMultipleFiles(args) {
|
|
99
|
+
const results = [];
|
|
100
|
+
const errors = [];
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
// Use FileUtils to normalize parameters
|
|
104
|
+
const { paths, contents, lines: lineArray, fileCount } = FileUtils.normalizeMultiFileArgs(args);
|
|
105
|
+
|
|
106
|
+
// Perform insert operation for each file
|
|
107
|
+
for (let i = 0; i < fileCount; i++) {
|
|
108
|
+
const filePath = paths[i];
|
|
109
|
+
const line = lineArray[i];
|
|
110
|
+
const content = contents[i];
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const result = await InsertContentHandler.insertContentSingleFile(filePath, line, content);
|
|
114
|
+
results.push({
|
|
115
|
+
file: filePath,
|
|
116
|
+
success: true,
|
|
117
|
+
message: result.content[0].text
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
120
|
+
errors.push({
|
|
121
|
+
file: filePath,
|
|
122
|
+
error: error.message
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch (paramError) {
|
|
127
|
+
throw new Error(`Parameter validation failed: ${paramError.message}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Build return message
|
|
131
|
+
let responseText = '';
|
|
132
|
+
|
|
133
|
+
if (results.length > 0) {
|
|
134
|
+
responseText += `Successfully processed ${results.length} file(s):\n\n`;
|
|
135
|
+
for (const result of results) {
|
|
136
|
+
responseText += `✅ ${result.file}: ${result.message}\n`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (errors.length > 0) {
|
|
141
|
+
responseText += `\nErrors encountered:\n`;
|
|
142
|
+
for (const error of errors) {
|
|
143
|
+
responseText += `❌ ${error.file}: ${error.error}\n`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (results.length === 0 && errors.length > 0) {
|
|
148
|
+
throw new Error(`Failed to insert content in any files: ${errors.map(e => e.error).join(', ')}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return FileUtils.createResponse(responseText.trim());
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -1,65 +1,54 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const displayPath = prefix ? `${prefix}/${item.name}` : item.name;
|
|
56
|
-
|
|
57
|
-
if (item.isDirectory()) {
|
|
58
|
-
result.push(`directory: ${displayPath}`);
|
|
59
|
-
await ListFilesHandler.listFilesRecursive(itemPath, result, displayPath);
|
|
60
|
-
} else {
|
|
61
|
-
result.push(`file: ${displayPath}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
1
|
+
import { FileUtils } from '../utils/fileUtils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* list_files tool handler
|
|
5
|
+
*/
|
|
6
|
+
export class ListFilesHandler {
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handle list_files request
|
|
10
|
+
* @param {Object} args - Request parameters
|
|
11
|
+
* @returns {Promise<Object>} Response result
|
|
12
|
+
*/
|
|
13
|
+
static async handle(args) {
|
|
14
|
+
const {
|
|
15
|
+
path,
|
|
16
|
+
recursive = false,
|
|
17
|
+
show_hidden = false,
|
|
18
|
+
git_filter = 'all'
|
|
19
|
+
} = args;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const result = await FileUtils.readDirectoryAdvanced(path, {
|
|
23
|
+
recursive,
|
|
24
|
+
show_hidden,
|
|
25
|
+
git_filter
|
|
26
|
+
}); // Build informative response message
|
|
27
|
+
let message = result.join('\n');
|
|
28
|
+
|
|
29
|
+
if (result.length === 0) {
|
|
30
|
+
message = 'No files found matching the specified criteria.';
|
|
31
|
+
} else {
|
|
32
|
+
// Add summary information
|
|
33
|
+
const summary = [];
|
|
34
|
+
if (!show_hidden) {
|
|
35
|
+
summary.push('hidden files excluded');
|
|
36
|
+
}
|
|
37
|
+
if (git_filter !== 'all') {
|
|
38
|
+
summary.push(`showing only ${git_filter} files`);
|
|
39
|
+
}
|
|
40
|
+
if (recursive) {
|
|
41
|
+
summary.push('recursive listing');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (summary.length > 0) {
|
|
45
|
+
message += `\n\n--- Listing options: ${summary.join(', ')} ---`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return FileUtils.createResponse(message);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`Failed to list files: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|