@mrxkun/mcfast-mcp 1.4.2 → 1.4.4
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 +1 -1
- package/src/index.js +109 -40
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -560,52 +560,115 @@ async function handleSearchCode({ query, files, regex = false, caseSensitive = f
|
|
|
560
560
|
const start = Date.now();
|
|
561
561
|
try {
|
|
562
562
|
const results = [];
|
|
563
|
-
const flags = caseSensitive ? 'm' : 'im';
|
|
564
|
-
const pattern = regex ? new RegExp(query, flags) : null;
|
|
565
|
-
const queryLower = query.toLowerCase();
|
|
566
563
|
let totalInputChars = 0;
|
|
567
564
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
565
|
+
// If regex mode, use original regex logic
|
|
566
|
+
if (regex) {
|
|
567
|
+
const flags = caseSensitive ? 'm' : 'im';
|
|
568
|
+
const pattern = new RegExp(query, flags);
|
|
569
|
+
|
|
570
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
571
|
+
if (typeof content !== 'string') continue;
|
|
572
|
+
totalInputChars += content.length;
|
|
573
|
+
|
|
574
|
+
const lines = content.split('\n');
|
|
575
|
+
lines.forEach((line, index) => {
|
|
576
|
+
if (pattern.test(line)) {
|
|
577
|
+
pattern.lastIndex = 0;
|
|
578
|
+
const startLine = Math.max(0, index - contextLines);
|
|
579
|
+
const endLine = Math.min(lines.length - 1, index + contextLines);
|
|
580
|
+
|
|
581
|
+
const contextSnippet = lines
|
|
582
|
+
.slice(startLine, endLine + 1)
|
|
583
|
+
.map((l, i) => ({
|
|
584
|
+
lineNumber: startLine + i + 1,
|
|
585
|
+
content: l,
|
|
586
|
+
isMatch: startLine + i === index
|
|
587
|
+
}));
|
|
588
|
+
|
|
589
|
+
results.push({
|
|
590
|
+
file: filePath,
|
|
591
|
+
lineNumber: index + 1,
|
|
592
|
+
matchedLine: line.trim(),
|
|
593
|
+
context: contextSnippet,
|
|
594
|
+
matchType: 'regex'
|
|
595
|
+
});
|
|
583
596
|
}
|
|
584
|
-
}
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
// Semantic search with stop words filtering
|
|
601
|
+
const queryLower = query.toLowerCase();
|
|
602
|
+
|
|
603
|
+
// Common English stop words to filter out
|
|
604
|
+
const stopWords = new Set([
|
|
605
|
+
'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'for', 'from', 'has', 'he',
|
|
606
|
+
'in', 'is', 'it', 'its', 'of', 'on', 'that', 'the', 'to', 'was', 'will', 'with',
|
|
607
|
+
'how', 'what', 'when', 'where', 'who', 'why', 'does', 'do', 'this', 'these', 'those'
|
|
608
|
+
]);
|
|
609
|
+
|
|
610
|
+
// Extract significant words (3+ chars, not stop words)
|
|
611
|
+
const words = queryLower
|
|
612
|
+
.split(/\W+/)
|
|
613
|
+
.filter(w => w.length >= 3 && !stopWords.has(w));
|
|
614
|
+
|
|
615
|
+
// If no significant words, fall back to whole query
|
|
616
|
+
const searchTerms = words.length > 0 ? words : [queryLower];
|
|
617
|
+
|
|
618
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
619
|
+
if (typeof content !== 'string') continue;
|
|
620
|
+
totalInputChars += content.length;
|
|
621
|
+
|
|
622
|
+
const lines = content.split('\n');
|
|
623
|
+
lines.forEach((line, index) => {
|
|
624
|
+
const lineLower = caseSensitive ? line : line.toLowerCase();
|
|
625
|
+
const searchQuery = caseSensitive ? query : queryLower;
|
|
626
|
+
|
|
627
|
+
// Check 1: Exact phrase match (highest priority)
|
|
628
|
+
const exactMatch = lineLower.includes(searchQuery);
|
|
629
|
+
|
|
630
|
+
// Check 2: All significant words present (semantic match)
|
|
631
|
+
const allWordsMatch = searchTerms.every(term => lineLower.includes(term));
|
|
632
|
+
|
|
633
|
+
// Check 3: At least half of significant words present (fuzzy match)
|
|
634
|
+
const matchCount = searchTerms.filter(term => lineLower.includes(term)).length;
|
|
635
|
+
const fuzzyMatch = matchCount >= Math.ceil(searchTerms.length / 2);
|
|
636
|
+
|
|
637
|
+
if (exactMatch || allWordsMatch || (searchTerms.length > 1 && fuzzyMatch)) {
|
|
638
|
+
const startLine = Math.max(0, index - contextLines);
|
|
639
|
+
const endLine = Math.min(lines.length - 1, index + contextLines);
|
|
640
|
+
|
|
641
|
+
const contextSnippet = lines
|
|
642
|
+
.slice(startLine, endLine + 1)
|
|
643
|
+
.map((l, i) => ({
|
|
644
|
+
lineNumber: startLine + i + 1,
|
|
645
|
+
content: l,
|
|
646
|
+
isMatch: startLine + i === index
|
|
647
|
+
}));
|
|
648
|
+
|
|
649
|
+
results.push({
|
|
650
|
+
file: filePath,
|
|
651
|
+
lineNumber: index + 1,
|
|
652
|
+
matchedLine: line.trim(),
|
|
653
|
+
context: contextSnippet,
|
|
654
|
+
matchType: exactMatch ? 'exact' : allWordsMatch ? 'semantic' : 'fuzzy',
|
|
655
|
+
matchScore: exactMatch ? 100 : allWordsMatch ? 80 : matchCount * 10
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
}
|
|
585
660
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const contextSnippet = lines
|
|
591
|
-
.slice(startLine, endLine + 1)
|
|
592
|
-
.map((l, i) => ({
|
|
593
|
-
lineNumber: startLine + i + 1,
|
|
594
|
-
content: l,
|
|
595
|
-
isMatch: startLine + i === index
|
|
596
|
-
}));
|
|
597
|
-
|
|
598
|
-
results.push({
|
|
599
|
-
file: filePath,
|
|
600
|
-
lineNumber: index + 1,
|
|
601
|
-
matchedLine: line.trim(),
|
|
602
|
-
context: contextSnippet
|
|
603
|
-
});
|
|
661
|
+
// Sort results: by score (highest first), then by file
|
|
662
|
+
results.sort((a, b) => {
|
|
663
|
+
if (a.matchScore !== b.matchScore) {
|
|
664
|
+
return b.matchScore - a.matchScore;
|
|
604
665
|
}
|
|
666
|
+
return a.file.localeCompare(b.file);
|
|
605
667
|
});
|
|
606
668
|
}
|
|
607
669
|
|
|
608
|
-
|
|
670
|
+
const searchType = regex ? 'regex' : 'semantic';
|
|
671
|
+
let output = `🔍 Found ${results.length} matches for "${query}" (${searchType} search)\n\n`;
|
|
609
672
|
if (results.length === 0) {
|
|
610
673
|
output += "No matches found in provided context.";
|
|
611
674
|
} else {
|
|
@@ -691,12 +754,16 @@ async function handleEditFile({ path: filePath, content, instruction = "" }) {
|
|
|
691
754
|
try {
|
|
692
755
|
await fs.writeFile(filePath, content, 'utf8');
|
|
693
756
|
|
|
757
|
+
const estimatedTokens = Math.ceil(content.length / 4);
|
|
758
|
+
|
|
694
759
|
reportAudit({
|
|
695
760
|
tool: 'edit_file',
|
|
696
761
|
instruction: instruction || `Written ${filePath}`,
|
|
697
762
|
status: 'success',
|
|
698
763
|
latency_ms: Date.now() - start,
|
|
699
|
-
files_count: 1
|
|
764
|
+
files_count: 1,
|
|
765
|
+
input_tokens: estimatedTokens,
|
|
766
|
+
output_tokens: 10 // Minimal output (success message)
|
|
700
767
|
});
|
|
701
768
|
|
|
702
769
|
return {
|
|
@@ -708,7 +775,9 @@ async function handleEditFile({ path: filePath, content, instruction = "" }) {
|
|
|
708
775
|
instruction: instruction || `Failed write ${filePath}`,
|
|
709
776
|
status: 'error',
|
|
710
777
|
error_message: error.message,
|
|
711
|
-
latency_ms: Date.now() - start
|
|
778
|
+
latency_ms: Date.now() - start,
|
|
779
|
+
input_tokens: Math.ceil((content?.length || 0) / 4),
|
|
780
|
+
output_tokens: 0
|
|
712
781
|
});
|
|
713
782
|
return {
|
|
714
783
|
content: [{ type: "text", text: `❌ Failed to write file: ${error.message}` }],
|