@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.
Files changed (3) hide show
  1. package/README.md +22 -1
  2. package/package.json +1 -1
  3. 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.7`.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrxkun/mcfast-mcp",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Ultra-fast code editing via Mercury Coder Cloud API.",
5
5
  "type": "module",
6
6
  "bin": {
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.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);