@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.
- package/package.json +4 -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.
|
|
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.
|
|
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: "
|
|
134
|
-
description: "
|
|
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
|
-
|
|
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 === "
|
|
289
|
-
return await
|
|
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();
|