@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +109 -40
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrxkun/mcfast-mcp",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "Ultra-fast code editing via Mercury Coder Cloud API.",
5
5
  "type": "module",
6
6
  "bin": {
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
- for (const [filePath, content] of Object.entries(files)) {
569
- if (typeof content !== 'string') continue;
570
- totalInputChars += content.length;
571
-
572
- const lines = content.split('\n');
573
- lines.forEach((line, index) => {
574
- let isMatch = false;
575
- if (regex && pattern) {
576
- isMatch = pattern.test(line);
577
- pattern.lastIndex = 0;
578
- } else {
579
- if (caseSensitive) {
580
- isMatch = line.includes(query);
581
- } else {
582
- isMatch = line.toLowerCase().includes(queryLower);
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
- if (isMatch) {
587
- const startLine = Math.max(0, index - contextLines);
588
- const endLine = Math.min(lines.length - 1, index + contextLines);
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
- let output = `🔍 Found ${results.length} matches for "${query}" (literal search)\n\n`;
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}` }],