magector 1.2.10 → 1.2.11
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 +8 -6
- package/src/mcp-server.js +251 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.11",
|
|
4
4
|
"description": "Semantic code search for Magento 2 — index, search, MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp-server.js",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"validate:keep": "node src/cli.js validate --verbose --keep",
|
|
22
22
|
"benchmark": "node src/cli.js benchmark",
|
|
23
23
|
"test": "node tests/mcp-server.test.js",
|
|
24
|
-
"test:no-index": "node tests/mcp-server.test.js --no-index"
|
|
24
|
+
"test:no-index": "node tests/mcp-server.test.js --no-index",
|
|
25
|
+
"test:accuracy": "node tests/mcp-accuracy.test.js",
|
|
26
|
+
"test:accuracy:verbose": "node tests/mcp-accuracy.test.js --verbose"
|
|
25
27
|
},
|
|
26
28
|
"dependencies": {
|
|
27
29
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
@@ -31,10 +33,10 @@
|
|
|
31
33
|
"ruvector": "^0.1.96"
|
|
32
34
|
},
|
|
33
35
|
"optionalDependencies": {
|
|
34
|
-
"@magector/cli-darwin-arm64": "1.2.
|
|
35
|
-
"@magector/cli-linux-x64": "1.2.
|
|
36
|
-
"@magector/cli-linux-arm64": "1.2.
|
|
37
|
-
"@magector/cli-win32-x64": "1.2.
|
|
36
|
+
"@magector/cli-darwin-arm64": "1.2.11",
|
|
37
|
+
"@magector/cli-linux-x64": "1.2.11",
|
|
38
|
+
"@magector/cli-linux-arm64": "1.2.11",
|
|
39
|
+
"@magector/cli-win32-x64": "1.2.11"
|
|
38
40
|
},
|
|
39
41
|
"keywords": [
|
|
40
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -14,7 +14,8 @@ import {
|
|
|
14
14
|
ListResourcesRequestSchema,
|
|
15
15
|
ReadResourceRequestSchema
|
|
16
16
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
17
|
-
import { execFileSync } from 'child_process';
|
|
17
|
+
import { execFileSync, spawn } from 'child_process';
|
|
18
|
+
import { createInterface } from 'readline';
|
|
18
19
|
import { existsSync } from 'fs';
|
|
19
20
|
import { stat } from 'fs/promises';
|
|
20
21
|
import { glob } from 'glob';
|
|
@@ -47,7 +48,112 @@ const rustEnv = {
|
|
|
47
48
|
RUST_LOG: 'error',
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Query cache: avoid re-embedding identical queries.
|
|
53
|
+
* Keyed by "query|limit", capped at 200 entries (LRU eviction).
|
|
54
|
+
*/
|
|
55
|
+
const searchCache = new Map();
|
|
56
|
+
const CACHE_MAX = 200;
|
|
57
|
+
|
|
58
|
+
function cacheSet(key, value) {
|
|
59
|
+
if (searchCache.size >= CACHE_MAX) {
|
|
60
|
+
const oldest = searchCache.keys().next().value;
|
|
61
|
+
searchCache.delete(oldest);
|
|
62
|
+
}
|
|
63
|
+
searchCache.set(key, value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Persistent Rust Serve Process ──────────────────────────────
|
|
67
|
+
// Keeps ONNX model + HNSW index loaded; eliminates ~2.6s cold start per query.
|
|
68
|
+
// Falls back to execFileSync if serve mode unavailable.
|
|
69
|
+
|
|
70
|
+
let serveProcess = null;
|
|
71
|
+
let serveReady = false;
|
|
72
|
+
let servePending = new Map();
|
|
73
|
+
let serveNextId = 1;
|
|
74
|
+
let serveReadline = null;
|
|
75
|
+
|
|
76
|
+
function startServeProcess() {
|
|
77
|
+
try {
|
|
78
|
+
const proc = spawn(config.rustBinary, [
|
|
79
|
+
'serve',
|
|
80
|
+
'-d', config.dbPath,
|
|
81
|
+
'-c', config.modelCache
|
|
82
|
+
], { stdio: ['pipe', 'pipe', 'pipe'], env: rustEnv });
|
|
83
|
+
|
|
84
|
+
proc.on('error', () => { serveProcess = null; serveReady = false; });
|
|
85
|
+
proc.on('exit', () => { serveProcess = null; serveReady = false; });
|
|
86
|
+
proc.stderr.on('data', () => {}); // drain stderr
|
|
87
|
+
|
|
88
|
+
serveReadline = createInterface({ input: proc.stdout });
|
|
89
|
+
serveReadline.on('line', (line) => {
|
|
90
|
+
let parsed;
|
|
91
|
+
try { parsed = JSON.parse(line); } catch { return; }
|
|
92
|
+
|
|
93
|
+
// First line is ready signal
|
|
94
|
+
if (parsed.ready) {
|
|
95
|
+
serveReady = true;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Route response to pending request by order (FIFO)
|
|
100
|
+
if (servePending.size > 0) {
|
|
101
|
+
const [id, resolver] = servePending.entries().next().value;
|
|
102
|
+
servePending.delete(id);
|
|
103
|
+
resolver.resolve(parsed);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
serveProcess = proc;
|
|
108
|
+
} catch {
|
|
109
|
+
serveProcess = null;
|
|
110
|
+
serveReady = false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function serveQuery(command, params = {}, timeoutMs = 30000) {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
const id = serveNextId++;
|
|
117
|
+
const timer = setTimeout(() => {
|
|
118
|
+
servePending.delete(id);
|
|
119
|
+
reject(new Error('Serve query timeout'));
|
|
120
|
+
}, timeoutMs);
|
|
121
|
+
servePending.set(id, {
|
|
122
|
+
resolve: (v) => { clearTimeout(timer); resolve(v); }
|
|
123
|
+
});
|
|
124
|
+
const msg = JSON.stringify({ command, ...params });
|
|
125
|
+
serveProcess.stdin.write(msg + '\n');
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function rustSearchAsync(query, limit = 10) {
|
|
130
|
+
const cacheKey = `${query}|${limit}`;
|
|
131
|
+
if (searchCache.has(cacheKey)) {
|
|
132
|
+
return searchCache.get(cacheKey);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Try persistent serve process first
|
|
136
|
+
if (serveProcess && serveReady) {
|
|
137
|
+
try {
|
|
138
|
+
const resp = await serveQuery('search', { query, limit });
|
|
139
|
+
if (resp.ok && resp.data) {
|
|
140
|
+
cacheSet(cacheKey, resp.data);
|
|
141
|
+
return resp.data;
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Fall through to execFileSync
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Fallback: cold-start execFileSync
|
|
149
|
+
return rustSearchSync(query, limit);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function rustSearchSync(query, limit = 10) {
|
|
153
|
+
const cacheKey = `${query}|${limit}`;
|
|
154
|
+
if (searchCache.has(cacheKey)) {
|
|
155
|
+
return searchCache.get(cacheKey);
|
|
156
|
+
}
|
|
51
157
|
const result = execFileSync(config.rustBinary, [
|
|
52
158
|
'search', query,
|
|
53
159
|
'-d', config.dbPath,
|
|
@@ -55,10 +161,18 @@ function rustSearch(query, limit = 10) {
|
|
|
55
161
|
'-l', String(limit),
|
|
56
162
|
'-f', 'json'
|
|
57
163
|
], { encoding: 'utf-8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'], env: rustEnv });
|
|
58
|
-
|
|
164
|
+
const parsed = JSON.parse(result);
|
|
165
|
+
cacheSet(cacheKey, parsed);
|
|
166
|
+
return parsed;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Keep backward compat: synchronous wrapper (used by tools)
|
|
170
|
+
function rustSearch(query, limit = 10) {
|
|
171
|
+
return rustSearchSync(query, limit);
|
|
59
172
|
}
|
|
60
173
|
|
|
61
174
|
function rustIndex(magentoRoot) {
|
|
175
|
+
searchCache.clear(); // invalidate cache on reindex
|
|
62
176
|
const result = execFileSync(config.rustBinary, [
|
|
63
177
|
'index',
|
|
64
178
|
'-m', magentoRoot,
|
|
@@ -72,7 +186,7 @@ function rustStats() {
|
|
|
72
186
|
const result = execFileSync(config.rustBinary, [
|
|
73
187
|
'stats',
|
|
74
188
|
'-d', config.dbPath
|
|
75
|
-
], { encoding: 'utf-8', timeout:
|
|
189
|
+
], { encoding: 'utf-8', timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'], env: rustEnv });
|
|
76
190
|
// Parse text output: "Total vectors: N" and "Embedding dim: N"
|
|
77
191
|
const vectors = result.match(/Total vectors:\s*(\d+)/)?.[1] || '0';
|
|
78
192
|
const dim = result.match(/Embedding dim:\s*(\d+)/)?.[1] || '384';
|
|
@@ -127,6 +241,7 @@ function normalizeResult(r) {
|
|
|
127
241
|
magentoType: meta.magento_type || meta.magentoType,
|
|
128
242
|
className: meta.class_name || meta.className,
|
|
129
243
|
methodName: meta.method_name || meta.methodName,
|
|
244
|
+
methods: meta.methods || [],
|
|
130
245
|
namespace: meta.namespace,
|
|
131
246
|
isPlugin: meta.is_plugin || meta.isPlugin,
|
|
132
247
|
isController: meta.is_controller || meta.isController,
|
|
@@ -140,6 +255,36 @@ function normalizeResult(r) {
|
|
|
140
255
|
};
|
|
141
256
|
}
|
|
142
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Re-rank results by boosting scores for metadata matches.
|
|
260
|
+
* @param {Array} results - normalized results
|
|
261
|
+
* @param {Object} boosts - e.g. { fileType: 'xml', pathContains: 'di.xml', isPlugin: true }
|
|
262
|
+
* @param {number} weight - boost multiplier (default 0.3 = 30% score bump per match)
|
|
263
|
+
*/
|
|
264
|
+
function rerank(results, boosts = {}, weight = 0.3) {
|
|
265
|
+
if (!boosts || Object.keys(boosts).length === 0) return results;
|
|
266
|
+
|
|
267
|
+
return results.map(r => {
|
|
268
|
+
let bonus = 0;
|
|
269
|
+
if (boosts.fileType && r.type === boosts.fileType) bonus += weight;
|
|
270
|
+
if (boosts.pathContains) {
|
|
271
|
+
const patterns = Array.isArray(boosts.pathContains) ? boosts.pathContains : [boosts.pathContains];
|
|
272
|
+
for (const p of patterns) {
|
|
273
|
+
if (r.path?.toLowerCase().includes(p.toLowerCase())) bonus += weight;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (boosts.isPlugin && r.isPlugin) bonus += weight;
|
|
277
|
+
if (boosts.isController && r.isController) bonus += weight;
|
|
278
|
+
if (boosts.isObserver && r.isObserver) bonus += weight;
|
|
279
|
+
if (boosts.isRepository && r.isRepository) bonus += weight;
|
|
280
|
+
if (boosts.isResolver && r.isResolver) bonus += weight;
|
|
281
|
+
if (boosts.isModel && r.isModel) bonus += weight;
|
|
282
|
+
if (boosts.isBlock && r.isBlock) bonus += weight;
|
|
283
|
+
if (boosts.magentoType && r.magentoType === boosts.magentoType) bonus += weight;
|
|
284
|
+
return { ...r, score: (r.score || 0) + bonus };
|
|
285
|
+
}).sort((a, b) => b.score - a.score);
|
|
286
|
+
}
|
|
287
|
+
|
|
143
288
|
function formatSearchResults(results) {
|
|
144
289
|
if (!results || results.length === 0) {
|
|
145
290
|
return 'No results found.';
|
|
@@ -508,7 +653,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
508
653
|
try {
|
|
509
654
|
switch (name) {
|
|
510
655
|
case 'magento_search': {
|
|
511
|
-
const raw =
|
|
656
|
+
const raw = await rustSearchAsync(args.query, args.limit || 10);
|
|
512
657
|
const results = raw.map(normalizeResult);
|
|
513
658
|
return {
|
|
514
659
|
content: [{
|
|
@@ -519,10 +664,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
519
664
|
}
|
|
520
665
|
|
|
521
666
|
case 'magento_find_class': {
|
|
522
|
-
const
|
|
523
|
-
const
|
|
667
|
+
const ns = args.namespace || '';
|
|
668
|
+
const query = `${args.className} ${ns}`.trim();
|
|
669
|
+
const raw = await rustSearchAsync(query, 30);
|
|
670
|
+
const classLower = args.className.toLowerCase();
|
|
524
671
|
const results = raw.map(normalizeResult).filter(r =>
|
|
525
|
-
r.className?.toLowerCase().includes(
|
|
672
|
+
r.className?.toLowerCase().includes(classLower) ||
|
|
673
|
+
r.path?.toLowerCase().includes(classLower.replace(/\\/g, '/'))
|
|
526
674
|
);
|
|
527
675
|
return {
|
|
528
676
|
content: [{
|
|
@@ -533,12 +681,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
533
681
|
}
|
|
534
682
|
|
|
535
683
|
case 'magento_find_method': {
|
|
536
|
-
const query = `
|
|
537
|
-
const raw =
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
r.
|
|
684
|
+
const query = `method ${args.methodName} function ${args.className || ''}`.trim();
|
|
685
|
+
const raw = await rustSearchAsync(query, 30);
|
|
686
|
+
const methodLower = args.methodName.toLowerCase();
|
|
687
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
688
|
+
r.methodName?.toLowerCase() === methodLower ||
|
|
689
|
+
r.methodName?.toLowerCase().includes(methodLower) ||
|
|
690
|
+
r.methods?.some(m => m.toLowerCase() === methodLower || m.toLowerCase().includes(methodLower)) ||
|
|
691
|
+
r.path?.toLowerCase().includes(methodLower)
|
|
541
692
|
);
|
|
693
|
+
// Boost exact method matches to top
|
|
694
|
+
results = results.map(r => {
|
|
695
|
+
const exact = r.methodName?.toLowerCase() === methodLower ||
|
|
696
|
+
r.methods?.some(m => m.toLowerCase() === methodLower);
|
|
697
|
+
return { ...r, score: (r.score || 0) + (exact ? 0.5 : 0) };
|
|
698
|
+
}).sort((a, b) => b.score - a.score);
|
|
542
699
|
return {
|
|
543
700
|
content: [{
|
|
544
701
|
type: 'text',
|
|
@@ -552,12 +709,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
552
709
|
if (args.configType && args.configType !== 'other') {
|
|
553
710
|
query = `${args.configType}.xml ${args.query}`;
|
|
554
711
|
}
|
|
555
|
-
const raw =
|
|
556
|
-
const
|
|
712
|
+
const raw = await rustSearchAsync(query, 30);
|
|
713
|
+
const pathBoost = args.configType ? [`${args.configType}.xml`] : ['.xml'];
|
|
714
|
+
let normalized = raw.map(normalizeResult);
|
|
715
|
+
// Prefer XML results when configType is specified, but don't hard-exclude
|
|
716
|
+
if (args.configType) {
|
|
717
|
+
const xmlOnly = normalized.filter(r =>
|
|
718
|
+
r.type === 'xml' || r.path?.endsWith('.xml') || r.path?.includes('.xml')
|
|
719
|
+
);
|
|
720
|
+
if (xmlOnly.length > 0) normalized = xmlOnly;
|
|
721
|
+
}
|
|
722
|
+
const results = rerank(normalized, { fileType: 'xml', pathContains: pathBoost });
|
|
557
723
|
return {
|
|
558
724
|
content: [{
|
|
559
725
|
type: 'text',
|
|
560
|
-
text: formatSearchResults(results)
|
|
726
|
+
text: formatSearchResults(results.slice(0, 10))
|
|
561
727
|
}]
|
|
562
728
|
};
|
|
563
729
|
}
|
|
@@ -566,12 +732,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
566
732
|
let query = args.query;
|
|
567
733
|
if (args.area) query = `${args.area} ${query}`;
|
|
568
734
|
query += ' template phtml';
|
|
569
|
-
const raw =
|
|
570
|
-
const results = raw.map(normalizeResult);
|
|
735
|
+
const raw = await rustSearchAsync(query, 15);
|
|
736
|
+
const results = rerank(raw.map(normalizeResult), { pathContains: ['.phtml'] });
|
|
571
737
|
return {
|
|
572
738
|
content: [{
|
|
573
739
|
type: 'text',
|
|
574
|
-
text: formatSearchResults(results)
|
|
740
|
+
text: formatSearchResults(results.slice(0, 10))
|
|
575
741
|
}]
|
|
576
742
|
};
|
|
577
743
|
}
|
|
@@ -602,45 +768,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
602
768
|
if (args.targetClass) query += ` ${args.targetClass}`;
|
|
603
769
|
if (args.targetMethod) query += ` ${args.targetMethod} before after around`;
|
|
604
770
|
|
|
605
|
-
const raw =
|
|
606
|
-
|
|
771
|
+
const raw = await rustSearchAsync(query, 30);
|
|
772
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
607
773
|
r.isPlugin || r.path?.includes('/Plugin/') || r.path?.includes('di.xml')
|
|
608
774
|
);
|
|
775
|
+
results = rerank(results, { isPlugin: true, pathContains: ['/Plugin/', 'di.xml'] });
|
|
609
776
|
|
|
610
777
|
return {
|
|
611
778
|
content: [{
|
|
612
779
|
type: 'text',
|
|
613
|
-
text: formatSearchResults(results)
|
|
780
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
614
781
|
}]
|
|
615
782
|
};
|
|
616
783
|
}
|
|
617
784
|
|
|
618
785
|
case 'magento_find_observer': {
|
|
619
786
|
const query = `event ${args.eventName} observer`;
|
|
620
|
-
const raw =
|
|
621
|
-
|
|
787
|
+
const raw = await rustSearchAsync(query, 30);
|
|
788
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
622
789
|
r.isObserver || r.path?.includes('/Observer/') || r.path?.includes('events.xml')
|
|
623
790
|
);
|
|
791
|
+
results = rerank(results, { isObserver: true, pathContains: ['events.xml', '/Observer/'] });
|
|
624
792
|
|
|
625
793
|
return {
|
|
626
794
|
content: [{
|
|
627
795
|
type: 'text',
|
|
628
|
-
text: `## Observers for event: ${args.eventName}\n\n` + formatSearchResults(results)
|
|
796
|
+
text: `## Observers for event: ${args.eventName}\n\n` + formatSearchResults(results.slice(0, 15))
|
|
629
797
|
}]
|
|
630
798
|
};
|
|
631
799
|
}
|
|
632
800
|
|
|
633
801
|
case 'magento_find_preference': {
|
|
634
|
-
const query = `preference ${args.interfaceName}`;
|
|
635
|
-
const raw =
|
|
636
|
-
|
|
802
|
+
const query = `preference ${args.interfaceName} di.xml type`;
|
|
803
|
+
const raw = await rustSearchAsync(query, 30);
|
|
804
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
637
805
|
r.path?.includes('di.xml')
|
|
638
806
|
);
|
|
807
|
+
results = rerank(results, { fileType: 'xml', pathContains: ['di.xml'] });
|
|
639
808
|
|
|
640
809
|
return {
|
|
641
810
|
content: [{
|
|
642
811
|
type: 'text',
|
|
643
|
-
text: `## Preferences for: ${args.interfaceName}\n\n` + formatSearchResults(results)
|
|
812
|
+
text: `## Preferences for: ${args.interfaceName}\n\n` + formatSearchResults(results.slice(0, 15))
|
|
644
813
|
}]
|
|
645
814
|
};
|
|
646
815
|
}
|
|
@@ -649,26 +818,40 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
649
818
|
let query = `webapi route ${args.query}`;
|
|
650
819
|
if (args.method) query += ` method="${args.method}"`;
|
|
651
820
|
|
|
652
|
-
const raw =
|
|
653
|
-
|
|
821
|
+
const raw = await rustSearchAsync(query, 30);
|
|
822
|
+
let results = rerank(raw.map(normalizeResult), { pathContains: ['webapi.xml'] });
|
|
654
823
|
|
|
655
824
|
return {
|
|
656
825
|
content: [{
|
|
657
826
|
type: 'text',
|
|
658
|
-
text: `## API Endpoints matching: ${args.query}\n\n` + formatSearchResults(results)
|
|
827
|
+
text: `## API Endpoints matching: ${args.query}\n\n` + formatSearchResults(results.slice(0, 15))
|
|
659
828
|
}]
|
|
660
829
|
};
|
|
661
830
|
}
|
|
662
831
|
|
|
663
832
|
case 'magento_find_controller': {
|
|
664
833
|
const parts = args.route.split('/');
|
|
665
|
-
|
|
834
|
+
// Map route to Magento namespace: catalog/product/view → Catalog Controller Product View
|
|
835
|
+
const namespaceParts = parts.map(p => p.charAt(0).toUpperCase() + p.slice(1));
|
|
836
|
+
const query = `${namespaceParts.join(' ')} controller execute action`;
|
|
666
837
|
|
|
667
|
-
const raw =
|
|
838
|
+
const raw = await rustSearchAsync(query, 30);
|
|
668
839
|
let results = raw.map(normalizeResult).filter(r =>
|
|
669
840
|
r.isController || r.path?.includes('/Controller/')
|
|
670
841
|
);
|
|
671
842
|
|
|
843
|
+
// Boost results whose path matches the route segments
|
|
844
|
+
if (parts.length >= 2) {
|
|
845
|
+
const pathPattern = parts.map(p => p.charAt(0).toUpperCase() + p.slice(1));
|
|
846
|
+
results.sort((a, b) => {
|
|
847
|
+
const aPath = a.path || '';
|
|
848
|
+
const bPath = b.path || '';
|
|
849
|
+
const aMatches = pathPattern.filter(p => aPath.includes(p)).length;
|
|
850
|
+
const bMatches = pathPattern.filter(p => bPath.includes(p)).length;
|
|
851
|
+
return bMatches - aMatches;
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
|
|
672
855
|
if (args.area) {
|
|
673
856
|
results = results.filter(r => r.area === args.area || r.path?.includes(`/${args.area}/`));
|
|
674
857
|
}
|
|
@@ -683,30 +866,32 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
683
866
|
|
|
684
867
|
case 'magento_find_block': {
|
|
685
868
|
const query = `block ${args.query}`;
|
|
686
|
-
const raw =
|
|
687
|
-
|
|
869
|
+
const raw = await rustSearchAsync(query, 30);
|
|
870
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
688
871
|
r.isBlock || r.path?.includes('/Block/')
|
|
689
872
|
);
|
|
873
|
+
results = rerank(results, { isBlock: true, pathContains: ['/Block/'] });
|
|
690
874
|
|
|
691
875
|
return {
|
|
692
876
|
content: [{
|
|
693
877
|
type: 'text',
|
|
694
|
-
text: formatSearchResults(results)
|
|
878
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
695
879
|
}]
|
|
696
880
|
};
|
|
697
881
|
}
|
|
698
882
|
|
|
699
883
|
case 'magento_find_cron': {
|
|
700
884
|
const query = `cron job ${args.jobName}`;
|
|
701
|
-
const raw =
|
|
702
|
-
|
|
885
|
+
const raw = await rustSearchAsync(query, 30);
|
|
886
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
703
887
|
r.path?.includes('crontab.xml') || r.path?.includes('/Cron/')
|
|
704
888
|
);
|
|
889
|
+
results = rerank(results, { pathContains: ['crontab.xml', '/Cron/'] });
|
|
705
890
|
|
|
706
891
|
return {
|
|
707
892
|
content: [{
|
|
708
893
|
type: 'text',
|
|
709
|
-
text: `## Cron jobs matching: ${args.jobName}\n\n` + formatSearchResults(results)
|
|
894
|
+
text: `## Cron jobs matching: ${args.jobName}\n\n` + formatSearchResults(results.slice(0, 15))
|
|
710
895
|
}]
|
|
711
896
|
};
|
|
712
897
|
}
|
|
@@ -715,37 +900,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
715
900
|
let query = `graphql ${args.query}`;
|
|
716
901
|
if (args.schemaType) query += ` ${args.schemaType}`;
|
|
717
902
|
|
|
718
|
-
const raw =
|
|
719
|
-
|
|
903
|
+
const raw = await rustSearchAsync(query, 40);
|
|
904
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
720
905
|
r.isResolver || r.path?.includes('/Resolver/') ||
|
|
721
906
|
r.path?.includes('.graphqls') || r.type === 'graphql'
|
|
722
907
|
);
|
|
908
|
+
results = rerank(results, { isResolver: true, pathContains: ['.graphqls', '/Resolver/'] });
|
|
723
909
|
|
|
724
910
|
return {
|
|
725
911
|
content: [{
|
|
726
912
|
type: 'text',
|
|
727
|
-
text: `## GraphQL matching: ${args.query}\n\n` + formatSearchResults(results)
|
|
913
|
+
text: `## GraphQL matching: ${args.query}\n\n` + formatSearchResults(results.slice(0, 15))
|
|
728
914
|
}]
|
|
729
915
|
};
|
|
730
916
|
}
|
|
731
917
|
|
|
732
918
|
case 'magento_find_db_schema': {
|
|
733
|
-
const query = `table ${args.tableName} column
|
|
734
|
-
const raw =
|
|
735
|
-
|
|
919
|
+
const query = `db_schema.xml table ${args.tableName} column declarative schema`;
|
|
920
|
+
const raw = await rustSearchAsync(query, 40);
|
|
921
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
736
922
|
r.path?.includes('db_schema.xml')
|
|
737
923
|
);
|
|
924
|
+
results = rerank(results, { fileType: 'xml', pathContains: ['db_schema.xml'] });
|
|
738
925
|
|
|
739
926
|
return {
|
|
740
927
|
content: [{
|
|
741
928
|
type: 'text',
|
|
742
|
-
text: `## Database schema for: ${args.tableName}\n\n` + formatSearchResults(results)
|
|
929
|
+
text: `## Database schema for: ${args.tableName}\n\n` + formatSearchResults(results.slice(0, 15))
|
|
743
930
|
}]
|
|
744
931
|
};
|
|
745
932
|
}
|
|
746
933
|
|
|
747
934
|
case 'magento_module_structure': {
|
|
748
|
-
const raw =
|
|
935
|
+
const raw = await rustSearchAsync(args.moduleName, 100);
|
|
749
936
|
const moduleName = args.moduleName.replace('_', '/');
|
|
750
937
|
const results = raw.map(normalizeResult).filter(r =>
|
|
751
938
|
r.path?.includes(moduleName) || r.module?.includes(args.moduleName)
|
|
@@ -914,9 +1101,25 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
914
1101
|
});
|
|
915
1102
|
|
|
916
1103
|
async function main() {
|
|
1104
|
+
// Try to start persistent Rust serve process for fast queries
|
|
1105
|
+
try {
|
|
1106
|
+
startServeProcess();
|
|
1107
|
+
// Give it a moment to load model+index
|
|
1108
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1109
|
+
} catch {
|
|
1110
|
+
// Non-fatal: falls back to execFileSync per query
|
|
1111
|
+
}
|
|
1112
|
+
|
|
917
1113
|
const transport = new StdioServerTransport();
|
|
918
1114
|
await server.connect(transport);
|
|
919
1115
|
console.error('Magector MCP server started (Rust core backend)');
|
|
920
1116
|
}
|
|
921
1117
|
|
|
1118
|
+
// Cleanup on exit
|
|
1119
|
+
process.on('exit', () => {
|
|
1120
|
+
if (serveProcess) {
|
|
1121
|
+
serveProcess.kill();
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
|
|
922
1125
|
main().catch(console.error);
|