@mrxkun/mcfast-mcp 1.0.7 → 1.0.9
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/README.md +22 -1
- package/package.json +1 -1
- package/src/index.js +134 -1
package/README.md
CHANGED
|
@@ -76,6 +76,27 @@ search: "localhost:3000"
|
|
|
76
76
|
replace: "api.example.com"
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
### `search_code` ⚡ (No API required)
|
|
80
|
+
Fast local pattern-based search. Works offline without token.
|
|
81
|
+
|
|
82
|
+
**Example:**
|
|
83
|
+
```
|
|
84
|
+
query: "fetchData"
|
|
85
|
+
files: { "src/api.js": "<content>" }
|
|
86
|
+
regex: false
|
|
87
|
+
contextLines: 2
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `reapply`
|
|
91
|
+
Smart retry for failed/incomplete edits. Auto-retries up to 3 times with enhanced context.
|
|
92
|
+
|
|
93
|
+
**Example:**
|
|
94
|
+
```
|
|
95
|
+
instruction: "Add error handling to all fetch calls"
|
|
96
|
+
files: { "src/api.js": "<content>" }
|
|
97
|
+
errorContext: "Previous attempt missed the catch block"
|
|
98
|
+
```
|
|
99
|
+
|
|
79
100
|
---
|
|
80
101
|
|
|
81
102
|
## 🧠 MCP Prompts
|
|
@@ -91,7 +112,7 @@ mcfast provides built-in strategies for AI agents (Claude Desktop, etc) to use t
|
|
|
91
112
|
## 🔒 Privacy & Security
|
|
92
113
|
|
|
93
114
|
- **Zero Persistence:** Code is processed in-memory and discarded immediately
|
|
94
|
-
- **Open Source Client:** Audit the source at [github.com/ndpmmo/mcfast](https://github.com/ndpmmo/mcfast). Current stable version: `1.0.
|
|
115
|
+
- **Open Source Client:** Audit the source at [github.com/ndpmmo/mcfast](https://github.com/ndpmmo/mcfast). Current stable version: `1.0.9`.
|
|
95
116
|
- **Token Masking:** Your `MCFAST_TOKEN` is never logged
|
|
96
117
|
|
|
97
118
|
---
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -22,11 +22,12 @@ if (!TOKEN) {
|
|
|
22
22
|
const server = new Server(
|
|
23
23
|
{
|
|
24
24
|
name: "mcfast",
|
|
25
|
-
version: "1.0.
|
|
25
|
+
version: "1.0.9",
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
capabilities: {
|
|
29
29
|
tools: {},
|
|
30
|
+
prompts: {},
|
|
30
31
|
},
|
|
31
32
|
}
|
|
32
33
|
);
|
|
@@ -123,6 +124,34 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
123
124
|
depth: { type: "number", description: "Max depth to traverse (default: 5)." }
|
|
124
125
|
}
|
|
125
126
|
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "search_code",
|
|
130
|
+
description: "Fast local code search using pattern matching. No API call required. Supports regex and literal search.",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
query: { type: "string", description: "Search pattern (literal or regex)" },
|
|
135
|
+
files: { type: "object", description: "Map of file paths to content to search within.", additionalProperties: { type: "string" } },
|
|
136
|
+
regex: { type: "boolean", description: "Treat query as regex (default: false)" },
|
|
137
|
+
contextLines: { type: "number", description: "Lines of context around matches (default: 2)" }
|
|
138
|
+
},
|
|
139
|
+
required: ["query", "files"]
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "reapply",
|
|
144
|
+
description: "Smart retry for failed or incomplete edits. Analyzes error context and re-attempts with adjusted strategy. Max 3 retries.",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
instruction: { type: "string", description: "Original instruction that failed" },
|
|
149
|
+
files: { type: "object", description: "Current file contents", additionalProperties: { type: "string" } },
|
|
150
|
+
errorContext: { type: "string", description: "Description of the error or incomplete result" },
|
|
151
|
+
attempt: { type: "number", description: "Current attempt number (auto-incremented, starts at 1)" }
|
|
152
|
+
},
|
|
153
|
+
required: ["instruction", "files"]
|
|
154
|
+
}
|
|
126
155
|
}
|
|
127
156
|
],
|
|
128
157
|
};
|
|
@@ -249,11 +278,115 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
249
278
|
return await handleEditFile(args);
|
|
250
279
|
} else if (name === "list_files_fast") {
|
|
251
280
|
return await handleListFiles(args);
|
|
281
|
+
} else if (name === "search_code") {
|
|
282
|
+
return await handleSearchCode(args);
|
|
283
|
+
} else if (name === "reapply") {
|
|
284
|
+
return await handleReapply(args);
|
|
252
285
|
}
|
|
253
286
|
|
|
254
287
|
throw new Error(`Tool not found: ${name}`);
|
|
255
288
|
});
|
|
256
289
|
|
|
290
|
+
// Smart retry handler
|
|
291
|
+
async function handleReapply({ instruction, files, errorContext = "", attempt = 1 }) {
|
|
292
|
+
const MAX_ATTEMPTS = 3;
|
|
293
|
+
|
|
294
|
+
if (attempt > MAX_ATTEMPTS) {
|
|
295
|
+
return {
|
|
296
|
+
content: [{ type: "text", text: `❌ Max retry attempts (${MAX_ATTEMPTS}) reached. Manual intervention required.\n\nOriginal instruction: ${instruction}\nLast error: ${errorContext}` }],
|
|
297
|
+
isError: true
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Enhance instruction based on error context
|
|
302
|
+
let enhancedInstruction = instruction;
|
|
303
|
+
if (errorContext) {
|
|
304
|
+
enhancedInstruction = `Previous attempt failed with: "${errorContext}"\n\nPlease retry with extra care:\n${instruction}`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const result = await handleApplyFast({ instruction: enhancedInstruction, files, dryRun: false });
|
|
309
|
+
return {
|
|
310
|
+
content: [{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: `✅ Reapply successful on attempt ${attempt}/${MAX_ATTEMPTS}\n\n${result.content[0].text}`
|
|
313
|
+
}]
|
|
314
|
+
};
|
|
315
|
+
} catch (error) {
|
|
316
|
+
// Recurse with incremented attempt
|
|
317
|
+
return handleReapply({
|
|
318
|
+
instruction,
|
|
319
|
+
files,
|
|
320
|
+
errorContext: error.message,
|
|
321
|
+
attempt: attempt + 1
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Local search implementation (no API required)
|
|
327
|
+
async function handleSearchCode({ query, files, regex = false, contextLines = 2 }) {
|
|
328
|
+
try {
|
|
329
|
+
const results = [];
|
|
330
|
+
const pattern = regex ? new RegExp(query, 'gim') : null;
|
|
331
|
+
|
|
332
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
333
|
+
if (typeof content !== 'string') continue;
|
|
334
|
+
|
|
335
|
+
const lines = content.split('\n');
|
|
336
|
+
lines.forEach((line, index) => {
|
|
337
|
+
let isMatch = false;
|
|
338
|
+
if (regex && pattern) {
|
|
339
|
+
isMatch = pattern.test(line);
|
|
340
|
+
pattern.lastIndex = 0; // Reset for global regex
|
|
341
|
+
} else {
|
|
342
|
+
isMatch = line.toLowerCase().includes(query.toLowerCase());
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (isMatch) {
|
|
346
|
+
const startLine = Math.max(0, index - contextLines);
|
|
347
|
+
const endLine = Math.min(lines.length - 1, index + contextLines);
|
|
348
|
+
|
|
349
|
+
const contextSnippet = lines
|
|
350
|
+
.slice(startLine, endLine + 1)
|
|
351
|
+
.map((l, i) => ({
|
|
352
|
+
lineNumber: startLine + i + 1,
|
|
353
|
+
content: l,
|
|
354
|
+
isMatch: startLine + i === index
|
|
355
|
+
}));
|
|
356
|
+
|
|
357
|
+
results.push({
|
|
358
|
+
file: filePath,
|
|
359
|
+
lineNumber: index + 1,
|
|
360
|
+
matchedLine: line.trim(),
|
|
361
|
+
context: contextSnippet
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let output = `🔍 Found ${results.length} matches for "${query}"\n\n`;
|
|
368
|
+
if (results.length === 0) {
|
|
369
|
+
output += "No matches found.";
|
|
370
|
+
} else {
|
|
371
|
+
results.slice(0, 50).forEach((result, i) => {
|
|
372
|
+
output += `📄 ${result.file}:${result.lineNumber}\n`;
|
|
373
|
+
result.context.forEach(ctx => {
|
|
374
|
+
const prefix = ctx.isMatch ? "→ " : " ";
|
|
375
|
+
output += `${prefix}${ctx.lineNumber}: ${ctx.content}\n`;
|
|
376
|
+
});
|
|
377
|
+
if (i < Math.min(results.length, 50) - 1) output += "\n";
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return { content: [{ type: "text", text: output }] };
|
|
382
|
+
} catch (error) {
|
|
383
|
+
return {
|
|
384
|
+
content: [{ type: "text", text: `❌ Search error: ${error.message}` }],
|
|
385
|
+
isError: true
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
257
390
|
async function handleListFiles({ path: dirPath = process.cwd(), depth = 5 }) {
|
|
258
391
|
try {
|
|
259
392
|
const files = await getFiles(dirPath, depth);
|