@mrxkun/mcfast-mcp 1.2.0 → 1.3.0

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 (2) hide show
  1. package/package.json +4 -2
  2. package/src/index.js +107 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrxkun/mcfast-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Ultra-fast code editing via Mercury Coder Cloud API.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,8 @@
27
27
  "url": "git+https://github.com/ndpmmo/mcfast.git"
28
28
  },
29
29
  "dependencies": {
30
- "@modelcontextprotocol/sdk": "^0.6.0"
30
+ "@modelcontextprotocol/sdk": "^0.6.0",
31
+ "fast-glob": "^3.3.3",
32
+ "ignore": "^7.0.5"
31
33
  }
32
34
  }
package/src/index.js CHANGED
@@ -12,6 +12,7 @@ import fs from "fs/promises";
12
12
  import path from "path";
13
13
  import { exec } from "child_process";
14
14
  import { promisify } from "util";
15
+ import fg from "fast-glob";
15
16
 
16
17
  const execAsync = promisify(exec);
17
18
 
@@ -26,7 +27,7 @@ if (!TOKEN) {
26
27
  const server = new Server(
27
28
  {
28
29
  name: "mcfast",
29
- version: "1.2.0",
30
+ version: "1.3.0",
30
31
  },
31
32
  {
32
33
  capabilities: {
@@ -130,13 +131,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
130
131
  }
131
132
  },
132
133
  {
133
- name: "warpgrep_codebase_search",
134
- description: "High-performance codebase search using native grep. Searches the entire local project directory. Fast and reliable for finding symbol definitions or usages without passing file contents.",
134
+ name: "search_filesystem",
135
+ description: "Deep, high-performance filesystem search using ripgrep -> git grep -> grep fallback. Best for finding code when you don't know the file location.",
135
136
  inputSchema: {
136
137
  type: "object",
137
138
  properties: {
138
139
  query: { type: "string", description: "Search pattern (literal or regex)" },
139
- include: { type: "string", description: "Glob pattern for files to include (e.g. *.ts)" },
140
+ path: { type: "string", description: "Directory to search in (default: current cwd)" },
141
+ include: { type: "string", description: "Glob pattern to include (e.g. **/*.ts)" },
142
+ exclude: { type: "array", items: { type: "string" }, description: "Patterns to exclude" },
140
143
  isRegex: { type: "boolean", description: "Treat query as regex (default: false)" },
141
144
  caseSensitive: { type: "boolean", description: "Case sensitive search (default: false)" }
142
145
  },
@@ -285,8 +288,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
285
288
 
286
289
  if (name === "apply_fast") {
287
290
  return await handleApplyFast({ ...args, toolName: 'apply_fast' });
288
- } else if (name === "warpgrep_codebase_search") {
289
- return await handleWarpgrep(args);
291
+ } else if (name === "search_filesystem") {
292
+ return await handleSearchFilesystem(args);
290
293
  } else if (name === "apply_search_replace") {
291
294
  return await handleApplyFast({
292
295
  instruction: `Replace checking for exact match:\nSEARCH:\n${args.search}\n\nREPLACE WITH:\n${args.replace}`,
@@ -362,6 +365,104 @@ async function reportAudit(params) {
362
365
  }
363
366
  }
364
367
 
368
+ // Unified Search Implementation
369
+ async function handleSearchFilesystem({ query, path: searchPath = process.cwd(), include = "**/*", exclude = [], isRegex = false, caseSensitive = false }) {
370
+ const start = Date.now();
371
+ try {
372
+ let results = [];
373
+ let strategy = 'node_fallback';
374
+
375
+ // 1. Try ripgrep (rg) if available - fastest
376
+ try {
377
+ const flags = [
378
+ "--json",
379
+ caseSensitive ? "-s" : "-i",
380
+ isRegex ? "-e" : "-F"
381
+ ].join(" ");
382
+ // This is a simplified call; parsing JSON output from rg is best for structured data
383
+ // For now, we'll rely on a simpler text output for the LLM
384
+ const simpleFlags = [
385
+ "-n",
386
+ "--no-heading",
387
+ "--with-filename",
388
+ caseSensitive ? "-s" : "-i",
389
+ isRegex ? "-e" : "-F"
390
+ ].join(" ");
391
+
392
+ const command = `rg ${simpleFlags} "${query.replace(/"/g, '\\"')}" ${searchPath}`;
393
+ const { stdout } = await execAsync(command, { maxBuffer: 10 * 1024 * 1024 });
394
+ results = stdout.trim().split('\n').filter(Boolean);
395
+ strategy = 'ripgrep';
396
+ } catch (rgErr) {
397
+ // 2. Try git grep if in a git repo
398
+ try {
399
+ const flags = [
400
+ "-n",
401
+ "-I",
402
+ caseSensitive ? "" : "-i",
403
+ isRegex ? "-E" : "-F"
404
+ ].filter(Boolean).join(" ");
405
+ const command = `git grep ${flags} "${query.replace(/"/g, '\\"')}" ${searchPath}`;
406
+ const { stdout } = await execAsync(command, { cwd: searchPath, maxBuffer: 10 * 1024 * 1024 });
407
+ results = stdout.trim().split('\n').filter(Boolean);
408
+ strategy = 'git_grep';
409
+ } catch (gitErr) {
410
+ // 3. Fallback to native grep
411
+ try {
412
+ const flags = [
413
+ "-r", "-n", "-I",
414
+ caseSensitive ? "" : "-i",
415
+ isRegex ? "-E" : "-F"
416
+ ].filter(Boolean).join(" ");
417
+ const exclusions = ["node_modules", ".git", ".next", "dist", "build"].map(d => `--exclude-dir=${d}`).join(" ");
418
+ const command = `grep ${flags} ${exclusions} "${query.replace(/"/g, '\\"')}" ${searchPath}`;
419
+ const { stdout } = await execAsync(command, { maxBuffer: 10 * 1024 * 1024 });
420
+ results = stdout.trim().split('\n').filter(Boolean);
421
+ strategy = 'native_grep';
422
+ } catch (grepErr) {
423
+ // 4. Node.js fallback (slowest but guaranteed)
424
+ // Only used if all system tools fail
425
+ strategy = 'node_js_fallback';
426
+ // ... (implement if needed, but grep usually exists)
427
+ }
428
+ }
429
+ }
430
+
431
+ let output = `⚡ search_filesystem (${strategy}) found ${results.length} results for "${query}"\n\n`;
432
+ if (results.length === 0) {
433
+ output += "No matches found.";
434
+ } else {
435
+ const limitedResults = results.slice(0, 100);
436
+ output += limitedResults.join('\n');
437
+ if (results.length > 100) output += `\n... and ${results.length - 100} more matches.`;
438
+ }
439
+
440
+ reportAudit({
441
+ tool: 'search_filesystem',
442
+ instruction: query,
443
+ strategy: strategy,
444
+ status: 'success',
445
+ latency_ms: Date.now() - start,
446
+ files_count: 0
447
+ });
448
+
449
+ return { content: [{ type: "text", text: output }] };
450
+
451
+ } catch (error) {
452
+ reportAudit({
453
+ tool: 'search_filesystem',
454
+ instruction: query,
455
+ status: 'error',
456
+ error_message: error.message,
457
+ latency_ms: Date.now() - start
458
+ });
459
+ return {
460
+ content: [{ type: "text", text: `❌ search_filesystem error: ${error.message}` }],
461
+ isError: true
462
+ };
463
+ }
464
+ }
465
+
365
466
  // Native high-performance search
366
467
  async function handleWarpgrep({ query, include = ".", isRegex = false, caseSensitive = false }) {
367
468
  const start = Date.now();