magector 1.2.10 → 1.2.12
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 +340 -122
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.12",
|
|
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.12",
|
|
37
|
+
"@magector/cli-linux-x64": "1.2.12",
|
|
38
|
+
"@magector/cli-linux-arm64": "1.2.12",
|
|
39
|
+
"@magector/cli-win32-x64": "1.2.12"
|
|
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,7 +241,9 @@ 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,
|
|
246
|
+
searchText: meta.search_text || meta.searchText || '',
|
|
131
247
|
isPlugin: meta.is_plugin || meta.isPlugin,
|
|
132
248
|
isController: meta.is_controller || meta.isController,
|
|
133
249
|
isObserver: meta.is_observer || meta.isObserver,
|
|
@@ -140,34 +256,78 @@ function normalizeResult(r) {
|
|
|
140
256
|
};
|
|
141
257
|
}
|
|
142
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Re-rank results by boosting scores for metadata matches.
|
|
261
|
+
* @param {Array} results - normalized results
|
|
262
|
+
* @param {Object} boosts - e.g. { fileType: 'xml', pathContains: 'di.xml', isPlugin: true }
|
|
263
|
+
* @param {number} weight - boost multiplier (default 0.3 = 30% score bump per match)
|
|
264
|
+
*/
|
|
265
|
+
function rerank(results, boosts = {}, weight = 0.3) {
|
|
266
|
+
if (!boosts || Object.keys(boosts).length === 0) return results;
|
|
267
|
+
|
|
268
|
+
return results.map(r => {
|
|
269
|
+
let bonus = 0;
|
|
270
|
+
if (boosts.fileType && r.type === boosts.fileType) bonus += weight;
|
|
271
|
+
if (boosts.pathContains) {
|
|
272
|
+
const patterns = Array.isArray(boosts.pathContains) ? boosts.pathContains : [boosts.pathContains];
|
|
273
|
+
for (const p of patterns) {
|
|
274
|
+
if (r.path?.toLowerCase().includes(p.toLowerCase())) bonus += weight;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (boosts.isPlugin && r.isPlugin) bonus += weight;
|
|
278
|
+
if (boosts.isController && r.isController) bonus += weight;
|
|
279
|
+
if (boosts.isObserver && r.isObserver) bonus += weight;
|
|
280
|
+
if (boosts.isRepository && r.isRepository) bonus += weight;
|
|
281
|
+
if (boosts.isResolver && r.isResolver) bonus += weight;
|
|
282
|
+
if (boosts.isModel && r.isModel) bonus += weight;
|
|
283
|
+
if (boosts.isBlock && r.isBlock) bonus += weight;
|
|
284
|
+
if (boosts.magentoType && r.magentoType === boosts.magentoType) bonus += weight;
|
|
285
|
+
return { ...r, score: (r.score || 0) + bonus };
|
|
286
|
+
}).sort((a, b) => b.score - a.score);
|
|
287
|
+
}
|
|
288
|
+
|
|
143
289
|
function formatSearchResults(results) {
|
|
144
290
|
if (!results || results.length === 0) {
|
|
145
|
-
return
|
|
291
|
+
return JSON.stringify({ results: [], count: 0 });
|
|
146
292
|
}
|
|
147
293
|
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (r.
|
|
166
|
-
if (r.
|
|
167
|
-
if (r.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
294
|
+
const formatted = results.map((r, i) => {
|
|
295
|
+
const entry = {
|
|
296
|
+
rank: i + 1,
|
|
297
|
+
score: r.score ? parseFloat(r.score.toFixed(3)) : null,
|
|
298
|
+
path: r.path || 'unknown',
|
|
299
|
+
};
|
|
300
|
+
if (r.module) entry.module = r.module;
|
|
301
|
+
if (r.className) entry.className = r.className;
|
|
302
|
+
if (r.namespace) entry.namespace = r.namespace;
|
|
303
|
+
if (r.methodName) entry.methodName = r.methodName;
|
|
304
|
+
if (r.methods && r.methods.length > 0) entry.methods = r.methods;
|
|
305
|
+
if (r.magentoType) entry.magentoType = r.magentoType;
|
|
306
|
+
if (r.type) entry.fileType = r.type;
|
|
307
|
+
if (r.area && r.area !== 'global') entry.area = r.area;
|
|
308
|
+
|
|
309
|
+
// Badges — concise role indicators
|
|
310
|
+
const badges = [];
|
|
311
|
+
if (r.isPlugin) badges.push('plugin');
|
|
312
|
+
if (r.isController) badges.push('controller');
|
|
313
|
+
if (r.isObserver) badges.push('observer');
|
|
314
|
+
if (r.isRepository) badges.push('repository');
|
|
315
|
+
if (r.isResolver) badges.push('graphql-resolver');
|
|
316
|
+
if (r.isModel) badges.push('model');
|
|
317
|
+
if (r.isBlock) badges.push('block');
|
|
318
|
+
if (badges.length > 0) entry.badges = badges;
|
|
319
|
+
|
|
320
|
+
// Snippet — first 300 chars of indexed content for quick assessment
|
|
321
|
+
if (r.searchText) {
|
|
322
|
+
entry.snippet = r.searchText.length > 300
|
|
323
|
+
? r.searchText.slice(0, 300) + '...'
|
|
324
|
+
: r.searchText;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return entry;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return JSON.stringify({ results: formatted, count: formatted.length });
|
|
171
331
|
}
|
|
172
332
|
|
|
173
333
|
// ─── MCP Server ─────────────────────────────────────────────────
|
|
@@ -189,17 +349,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
189
349
|
tools: [
|
|
190
350
|
{
|
|
191
351
|
name: 'magento_search',
|
|
192
|
-
description: 'Search Magento codebase semantically
|
|
352
|
+
description: 'Search Magento codebase semantically — find any PHP class, method, XML config, PHTML template, JS file, or GraphQL schema by describing what you need in natural language. Use this as a general-purpose search when no specialized tool fits. See also: magento_find_class, magento_find_method, magento_find_config for targeted searches.',
|
|
193
353
|
inputSchema: {
|
|
194
354
|
type: 'object',
|
|
195
355
|
properties: {
|
|
196
356
|
query: {
|
|
197
357
|
type: 'string',
|
|
198
|
-
description: 'Natural language search query
|
|
358
|
+
description: 'Natural language search query describing what you want to find. Examples: "product price calculation logic", "checkout controller", "customer authentication", "add to cart", "order placement flow"'
|
|
199
359
|
},
|
|
200
360
|
limit: {
|
|
201
361
|
type: 'number',
|
|
202
|
-
description: 'Maximum results to return (default: 10)',
|
|
362
|
+
description: 'Maximum results to return (default: 10, max: 100)',
|
|
203
363
|
default: 10
|
|
204
364
|
}
|
|
205
365
|
},
|
|
@@ -208,17 +368,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
208
368
|
},
|
|
209
369
|
{
|
|
210
370
|
name: 'magento_find_class',
|
|
211
|
-
description: 'Find a
|
|
371
|
+
description: 'Find a PHP class, interface, abstract class, or trait by name in Magento. Locates repositories, models, resource models, blocks, helpers, controllers, API interfaces, and data objects. See also: magento_find_plugin (interceptors for this class), magento_find_preference (DI overrides), magento_find_method (methods in the class).',
|
|
212
372
|
inputSchema: {
|
|
213
373
|
type: 'object',
|
|
214
374
|
properties: {
|
|
215
375
|
className: {
|
|
216
376
|
type: 'string',
|
|
217
|
-
description: '
|
|
377
|
+
description: 'Full or partial PHP class name. Examples: "ProductRepository", "AbstractModel", "CartManagementInterface", "CustomerData", "StockItemRepository"'
|
|
218
378
|
},
|
|
219
379
|
namespace: {
|
|
220
380
|
type: 'string',
|
|
221
|
-
description: 'Optional namespace filter'
|
|
381
|
+
description: 'Optional PHP namespace filter to narrow results. Example: "Magento\\Catalog\\Model"'
|
|
222
382
|
}
|
|
223
383
|
},
|
|
224
384
|
required: ['className']
|
|
@@ -226,17 +386,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
226
386
|
},
|
|
227
387
|
{
|
|
228
388
|
name: 'magento_find_method',
|
|
229
|
-
description: 'Find implementations of a
|
|
389
|
+
description: 'Find implementations of a PHP method or function across the Magento codebase. Searches method names, function definitions, and class method lists. See also: magento_find_class (parent class), magento_find_plugin (interceptors around this method).',
|
|
230
390
|
inputSchema: {
|
|
231
391
|
type: 'object',
|
|
232
392
|
properties: {
|
|
233
393
|
methodName: {
|
|
234
394
|
type: 'string',
|
|
235
|
-
description: '
|
|
395
|
+
description: 'PHP method or function name to find. Examples: "execute", "getPrice", "save", "getById", "getList", "beforeSave", "afterDelete", "toHtml", "dispatch"'
|
|
236
396
|
},
|
|
237
397
|
className: {
|
|
238
398
|
type: 'string',
|
|
239
|
-
description: 'Optional class name
|
|
399
|
+
description: 'Optional class name to narrow method search. Example: "ProductRepository"'
|
|
240
400
|
}
|
|
241
401
|
},
|
|
242
402
|
required: ['methodName']
|
|
@@ -244,18 +404,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
244
404
|
},
|
|
245
405
|
{
|
|
246
406
|
name: 'magento_find_config',
|
|
247
|
-
description: 'Find XML configuration files and nodes in Magento',
|
|
407
|
+
description: 'Find XML configuration files and nodes in Magento — di.xml (dependency injection), events.xml (observers), routes.xml (routing), system.xml (admin config), webapi.xml (REST/SOAP), module.xml (module declarations), layout XML. See also: magento_find_observer (events.xml), magento_find_preference (di.xml), magento_find_api (webapi.xml).',
|
|
248
408
|
inputSchema: {
|
|
249
409
|
type: 'object',
|
|
250
410
|
properties: {
|
|
251
411
|
query: {
|
|
252
412
|
type: 'string',
|
|
253
|
-
description: '
|
|
413
|
+
description: 'What configuration to find. Examples: "di.xml preference for ProductRepository", "routes.xml catalog", "system.xml payment field", "events.xml checkout", "layout xml catalog_product_view"'
|
|
254
414
|
},
|
|
255
415
|
configType: {
|
|
256
416
|
type: 'string',
|
|
257
417
|
enum: ['di', 'routes', 'system', 'events', 'webapi', 'module', 'layout', 'other'],
|
|
258
|
-
description: 'Type of configuration'
|
|
418
|
+
description: 'Type of XML configuration: di (dependency injection/preferences/virtualTypes), routes (URL routing), system (admin config fields/sections), events (event observers/listeners), webapi (REST/SOAP endpoint definitions), module (module.xml declarations/setup_version), layout (page layout XML/blocks/containers)'
|
|
259
419
|
}
|
|
260
420
|
},
|
|
261
421
|
required: ['query']
|
|
@@ -263,18 +423,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
263
423
|
},
|
|
264
424
|
{
|
|
265
425
|
name: 'magento_find_template',
|
|
266
|
-
description: 'Find PHTML
|
|
426
|
+
description: 'Find PHTML template files in Magento for frontend or admin rendering. Locates view templates for product pages, checkout, customer account, cart, CMS, catalog listing, and more. See also: magento_find_block (Block class rendering the template).',
|
|
267
427
|
inputSchema: {
|
|
268
428
|
type: 'object',
|
|
269
429
|
properties: {
|
|
270
430
|
query: {
|
|
271
431
|
type: 'string',
|
|
272
|
-
description: 'Template description
|
|
432
|
+
description: 'Template description or filename pattern. Examples: "product listing", "checkout form", "customer account dashboard", "minicart", "breadcrumbs", "category view", "order summary"'
|
|
273
433
|
},
|
|
274
434
|
area: {
|
|
275
435
|
type: 'string',
|
|
276
436
|
enum: ['frontend', 'adminhtml', 'base'],
|
|
277
|
-
description: 'Magento area'
|
|
437
|
+
description: 'Magento area: frontend (customer-facing storefront), adminhtml (admin panel), base (shared/fallback)'
|
|
278
438
|
}
|
|
279
439
|
},
|
|
280
440
|
required: ['query']
|
|
@@ -282,20 +442,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
282
442
|
},
|
|
283
443
|
{
|
|
284
444
|
name: 'magento_index',
|
|
285
|
-
description: 'Index or re-index Magento codebase for semantic search
|
|
445
|
+
description: 'Index or re-index the Magento codebase for semantic search. Run this after code changes to update the search index. Indexes PHP, XML, JS, PHTML, and GraphQL files.',
|
|
286
446
|
inputSchema: {
|
|
287
447
|
type: 'object',
|
|
288
448
|
properties: {
|
|
289
449
|
path: {
|
|
290
450
|
type: 'string',
|
|
291
|
-
description: '
|
|
451
|
+
description: 'Absolute path to Magento 2 root directory. Uses configured MAGENTO_ROOT if not specified.'
|
|
292
452
|
},
|
|
293
453
|
}
|
|
294
454
|
}
|
|
295
455
|
},
|
|
296
456
|
{
|
|
297
457
|
name: 'magento_stats',
|
|
298
|
-
description: 'Get index statistics
|
|
458
|
+
description: 'Get index statistics — total indexed vectors, embedding dimensions, and database path. Use this to verify the index is loaded and check its size.',
|
|
299
459
|
inputSchema: {
|
|
300
460
|
type: 'object',
|
|
301
461
|
properties: {}
|
|
@@ -303,30 +463,30 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
303
463
|
},
|
|
304
464
|
{
|
|
305
465
|
name: 'magento_find_plugin',
|
|
306
|
-
description: 'Find plugins (interceptors)
|
|
466
|
+
description: 'Find Magento plugins (interceptors) that modify class behavior via before/after/around methods. Locates Plugin classes and di.xml interceptor declarations. See also: magento_find_class (target class details), magento_find_method (intercepted method), magento_find_config with configType=di.',
|
|
307
467
|
inputSchema: {
|
|
308
468
|
type: 'object',
|
|
309
469
|
properties: {
|
|
310
470
|
targetClass: {
|
|
311
471
|
type: 'string',
|
|
312
|
-
description: 'Class being intercepted
|
|
472
|
+
description: 'Class being intercepted by plugins. Examples: "ProductRepository", "CartManagement", "CustomerRepository", "OrderRepository", "Topmenu"'
|
|
313
473
|
},
|
|
314
474
|
targetMethod: {
|
|
315
475
|
type: 'string',
|
|
316
|
-
description: '
|
|
476
|
+
description: 'Specific method being intercepted. Examples: "save", "getList", "getById", "getHtml", "dispatch"'
|
|
317
477
|
}
|
|
318
478
|
}
|
|
319
479
|
}
|
|
320
480
|
},
|
|
321
481
|
{
|
|
322
482
|
name: 'magento_find_observer',
|
|
323
|
-
description: 'Find observers for a
|
|
483
|
+
description: 'Find event observers (listeners) for a Magento event. Locates Observer classes and events.xml declarations. See also: magento_find_config with configType=events for raw XML.',
|
|
324
484
|
inputSchema: {
|
|
325
485
|
type: 'object',
|
|
326
486
|
properties: {
|
|
327
487
|
eventName: {
|
|
328
488
|
type: 'string',
|
|
329
|
-
description: '
|
|
489
|
+
description: 'Magento event name. Examples: "checkout_cart_add_product_complete", "sales_order_place_after", "catalog_product_save_after", "customer_login", "controller_action_predispatch"'
|
|
330
490
|
}
|
|
331
491
|
},
|
|
332
492
|
required: ['eventName']
|
|
@@ -334,13 +494,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
334
494
|
},
|
|
335
495
|
{
|
|
336
496
|
name: 'magento_find_preference',
|
|
337
|
-
description: 'Find DI preference overrides
|
|
497
|
+
description: 'Find DI preference overrides — which concrete class implements an interface or replaces another class via di.xml. See also: magento_find_class (implementation details), magento_find_config with configType=di.',
|
|
338
498
|
inputSchema: {
|
|
339
499
|
type: 'object',
|
|
340
500
|
properties: {
|
|
341
501
|
interfaceName: {
|
|
342
502
|
type: 'string',
|
|
343
|
-
description: 'Interface or class name to find
|
|
503
|
+
description: 'Interface or class name to find preference/implementation for. Examples: "ProductRepositoryInterface", "StoreManagerInterface", "LoggerInterface", "OrderRepositoryInterface", "CustomerRepositoryInterface"'
|
|
344
504
|
}
|
|
345
505
|
},
|
|
346
506
|
required: ['interfaceName']
|
|
@@ -348,18 +508,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
348
508
|
},
|
|
349
509
|
{
|
|
350
510
|
name: 'magento_find_api',
|
|
351
|
-
description: 'Find REST
|
|
511
|
+
description: 'Find REST and SOAP API endpoint definitions in webapi.xml and their service class implementations. See also: magento_find_config with configType=webapi, magento_find_class (service class).',
|
|
352
512
|
inputSchema: {
|
|
353
513
|
type: 'object',
|
|
354
514
|
properties: {
|
|
355
515
|
query: {
|
|
356
516
|
type: 'string',
|
|
357
|
-
description: 'API endpoint URL pattern or service method
|
|
517
|
+
description: 'API endpoint URL pattern or service method name. Examples: "/V1/products", "/V1/orders", "/V1/carts", "/V1/customers", "/V1/categories", "getList", "save"'
|
|
358
518
|
},
|
|
359
519
|
method: {
|
|
360
520
|
type: 'string',
|
|
361
521
|
enum: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
362
|
-
description: 'HTTP method
|
|
522
|
+
description: 'Filter by HTTP method: GET (read), POST (create), PUT (update), DELETE (remove)'
|
|
363
523
|
}
|
|
364
524
|
},
|
|
365
525
|
required: ['query']
|
|
@@ -367,18 +527,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
367
527
|
},
|
|
368
528
|
{
|
|
369
529
|
name: 'magento_find_controller',
|
|
370
|
-
description: 'Find controllers by
|
|
530
|
+
description: 'Find MVC controllers by frontend or admin route path. Maps URL routes to Controller action classes with execute() method. See also: magento_find_config with configType=routes.',
|
|
371
531
|
inputSchema: {
|
|
372
532
|
type: 'object',
|
|
373
533
|
properties: {
|
|
374
534
|
route: {
|
|
375
535
|
type: 'string',
|
|
376
|
-
description: '
|
|
536
|
+
description: 'URL route path in frontName/controller/action format. Examples: "catalog/product/view", "checkout/cart/add", "customer/account/login", "sales/order/view", "cms/page/view", "wishlist/index/add"'
|
|
377
537
|
},
|
|
378
538
|
area: {
|
|
379
539
|
type: 'string',
|
|
380
540
|
enum: ['frontend', 'adminhtml'],
|
|
381
|
-
description: 'Magento area'
|
|
541
|
+
description: 'Magento area: frontend (storefront routes) or adminhtml (admin panel routes)'
|
|
382
542
|
}
|
|
383
543
|
},
|
|
384
544
|
required: ['route']
|
|
@@ -386,13 +546,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
386
546
|
},
|
|
387
547
|
{
|
|
388
548
|
name: 'magento_find_block',
|
|
389
|
-
description: 'Find Block classes by
|
|
549
|
+
description: 'Find Magento Block classes used for view rendering and template logic. Blocks bridge controllers and templates. See also: magento_find_template (PHTML template rendered by the block), magento_find_config with configType=layout.',
|
|
390
550
|
inputSchema: {
|
|
391
551
|
type: 'object',
|
|
392
552
|
properties: {
|
|
393
553
|
query: {
|
|
394
554
|
type: 'string',
|
|
395
|
-
description: 'Block class name or functionality
|
|
555
|
+
description: 'Block class name or functionality description. Examples: "Product\\View", "cart totals", "category listing", "customer account navigation", "order view", "Topmenu"'
|
|
396
556
|
}
|
|
397
557
|
},
|
|
398
558
|
required: ['query']
|
|
@@ -400,13 +560,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
400
560
|
},
|
|
401
561
|
{
|
|
402
562
|
name: 'magento_find_cron',
|
|
403
|
-
description: 'Find cron jobs
|
|
563
|
+
description: 'Find scheduled cron jobs defined in crontab.xml and their handler classes in Cron/ directories. See also: magento_find_config for crontab.xml raw XML.',
|
|
404
564
|
inputSchema: {
|
|
405
565
|
type: 'object',
|
|
406
566
|
properties: {
|
|
407
567
|
jobName: {
|
|
408
568
|
type: 'string',
|
|
409
|
-
description: 'Cron job name or
|
|
569
|
+
description: 'Cron job name or keyword. Examples: "catalog_product", "indexer", "sitemap", "currency", "newsletter", "reindex", "aggregate", "clean"'
|
|
410
570
|
}
|
|
411
571
|
},
|
|
412
572
|
required: ['jobName']
|
|
@@ -414,18 +574,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
414
574
|
},
|
|
415
575
|
{
|
|
416
576
|
name: 'magento_find_graphql',
|
|
417
|
-
description: 'Find GraphQL types, queries, mutations,
|
|
577
|
+
description: 'Find GraphQL schema definitions (.graphqls), types, queries, mutations, and resolver PHP classes. See also: magento_find_class (resolver implementation), magento_find_method (resolver execute method).',
|
|
418
578
|
inputSchema: {
|
|
419
579
|
type: 'object',
|
|
420
580
|
properties: {
|
|
421
581
|
query: {
|
|
422
582
|
type: 'string',
|
|
423
|
-
description: 'GraphQL type, query, or
|
|
583
|
+
description: 'GraphQL type, query, mutation, or interface name. Examples: "products", "createCustomer", "CartItemInterface", "cart", "categoryList", "placeOrder", "createEmptyCart"'
|
|
424
584
|
},
|
|
425
585
|
schemaType: {
|
|
426
586
|
type: 'string',
|
|
427
587
|
enum: ['type', 'query', 'mutation', 'interface', 'resolver'],
|
|
428
|
-
description: '
|
|
588
|
+
description: 'Filter by GraphQL schema element: type (object types), query (read operations), mutation (write operations), interface (shared contracts), resolver (PHP resolver classes)'
|
|
429
589
|
}
|
|
430
590
|
},
|
|
431
591
|
required: ['query']
|
|
@@ -433,13 +593,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
433
593
|
},
|
|
434
594
|
{
|
|
435
595
|
name: 'magento_find_db_schema',
|
|
436
|
-
description: 'Find database table definitions and
|
|
596
|
+
description: 'Find database table definitions, columns, indexes, and constraints declared in db_schema.xml (Magento declarative schema). See also: magento_find_class (model/resource model for the table).',
|
|
437
597
|
inputSchema: {
|
|
438
598
|
type: 'object',
|
|
439
599
|
properties: {
|
|
440
600
|
tableName: {
|
|
441
601
|
type: 'string',
|
|
442
|
-
description: '
|
|
602
|
+
description: 'Database table name or pattern. Examples: "catalog_product_entity", "sales_order", "customer_entity", "quote", "cms_page", "inventory_source"'
|
|
443
603
|
}
|
|
444
604
|
},
|
|
445
605
|
required: ['tableName']
|
|
@@ -447,13 +607,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
447
607
|
},
|
|
448
608
|
{
|
|
449
609
|
name: 'magento_module_structure',
|
|
450
|
-
description: 'Get complete structure of a Magento module
|
|
610
|
+
description: 'Get the complete structure of a Magento module — lists all controllers, models, blocks, plugins, observers, API classes, XML configs, and templates. Provides an overview of module architecture.',
|
|
451
611
|
inputSchema: {
|
|
452
612
|
type: 'object',
|
|
453
613
|
properties: {
|
|
454
614
|
moduleName: {
|
|
455
615
|
type: 'string',
|
|
456
|
-
description: 'Full module name
|
|
616
|
+
description: 'Full Magento module name in Vendor_Module format. Examples: "Magento_Catalog", "Magento_Sales", "Magento_Customer", "Magento_Checkout", "Vendor_CustomModule"'
|
|
457
617
|
}
|
|
458
618
|
},
|
|
459
619
|
required: ['moduleName']
|
|
@@ -461,17 +621,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
461
621
|
},
|
|
462
622
|
{
|
|
463
623
|
name: 'magento_analyze_diff',
|
|
464
|
-
description: 'Analyze git diffs for risk scoring, change classification, and per-file analysis. Works on commits or staged changes.',
|
|
624
|
+
description: 'Analyze git diffs for risk scoring, change classification, and per-file impact analysis. Works on specific commits or staged changes. Useful for code review.',
|
|
465
625
|
inputSchema: {
|
|
466
626
|
type: 'object',
|
|
467
627
|
properties: {
|
|
468
628
|
commitHash: {
|
|
469
629
|
type: 'string',
|
|
470
|
-
description: 'Git commit hash to analyze. If omitted, analyzes staged changes.'
|
|
630
|
+
description: 'Git commit hash to analyze. If omitted, analyzes currently staged (git add) changes instead.'
|
|
471
631
|
},
|
|
472
632
|
staged: {
|
|
473
633
|
type: 'boolean',
|
|
474
|
-
description: '
|
|
634
|
+
description: 'Set true to analyze staged changes, false to require commitHash. Default: true.',
|
|
475
635
|
default: true
|
|
476
636
|
}
|
|
477
637
|
}
|
|
@@ -479,21 +639,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
479
639
|
},
|
|
480
640
|
{
|
|
481
641
|
name: 'magento_complexity',
|
|
482
|
-
description: 'Analyze code complexity
|
|
642
|
+
description: 'Analyze code complexity — cyclomatic complexity, function count, and line count for PHP files. Identifies complex hotspots and rates each file. Use for refactoring prioritization.',
|
|
483
643
|
inputSchema: {
|
|
484
644
|
type: 'object',
|
|
485
645
|
properties: {
|
|
486
646
|
module: {
|
|
487
647
|
type: 'string',
|
|
488
|
-
description: 'Magento module to analyze
|
|
648
|
+
description: 'Magento module to analyze. Finds all PHP files in the module. Examples: "Magento_Catalog", "Magento_Checkout", "Magento_Sales"'
|
|
489
649
|
},
|
|
490
650
|
path: {
|
|
491
651
|
type: 'string',
|
|
492
|
-
description: 'Specific file or directory path to analyze'
|
|
652
|
+
description: 'Specific file or directory path to analyze instead of a module name'
|
|
493
653
|
},
|
|
494
654
|
threshold: {
|
|
495
655
|
type: 'number',
|
|
496
|
-
description: 'Minimum cyclomatic complexity to report (
|
|
656
|
+
description: 'Minimum cyclomatic complexity to report. Set higher (e.g., 10) to only see complex files. Default: 0 (show all)',
|
|
497
657
|
default: 0
|
|
498
658
|
}
|
|
499
659
|
}
|
|
@@ -508,7 +668,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
508
668
|
try {
|
|
509
669
|
switch (name) {
|
|
510
670
|
case 'magento_search': {
|
|
511
|
-
const raw =
|
|
671
|
+
const raw = await rustSearchAsync(args.query, args.limit || 10);
|
|
512
672
|
const results = raw.map(normalizeResult);
|
|
513
673
|
return {
|
|
514
674
|
content: [{
|
|
@@ -519,10 +679,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
519
679
|
}
|
|
520
680
|
|
|
521
681
|
case 'magento_find_class': {
|
|
522
|
-
const
|
|
523
|
-
const
|
|
682
|
+
const ns = args.namespace || '';
|
|
683
|
+
const query = `${args.className} ${ns}`.trim();
|
|
684
|
+
const raw = await rustSearchAsync(query, 30);
|
|
685
|
+
const classLower = args.className.toLowerCase();
|
|
524
686
|
const results = raw.map(normalizeResult).filter(r =>
|
|
525
|
-
r.className?.toLowerCase().includes(
|
|
687
|
+
r.className?.toLowerCase().includes(classLower) ||
|
|
688
|
+
r.path?.toLowerCase().includes(classLower.replace(/\\/g, '/'))
|
|
526
689
|
);
|
|
527
690
|
return {
|
|
528
691
|
content: [{
|
|
@@ -533,12 +696,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
533
696
|
}
|
|
534
697
|
|
|
535
698
|
case 'magento_find_method': {
|
|
536
|
-
const query = `
|
|
537
|
-
const raw =
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
r.
|
|
699
|
+
const query = `method ${args.methodName} function ${args.className || ''}`.trim();
|
|
700
|
+
const raw = await rustSearchAsync(query, 30);
|
|
701
|
+
const methodLower = args.methodName.toLowerCase();
|
|
702
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
703
|
+
r.methodName?.toLowerCase() === methodLower ||
|
|
704
|
+
r.methodName?.toLowerCase().includes(methodLower) ||
|
|
705
|
+
r.methods?.some(m => m.toLowerCase() === methodLower || m.toLowerCase().includes(methodLower)) ||
|
|
706
|
+
r.path?.toLowerCase().includes(methodLower)
|
|
541
707
|
);
|
|
708
|
+
// Boost exact method matches to top
|
|
709
|
+
results = results.map(r => {
|
|
710
|
+
const exact = r.methodName?.toLowerCase() === methodLower ||
|
|
711
|
+
r.methods?.some(m => m.toLowerCase() === methodLower);
|
|
712
|
+
return { ...r, score: (r.score || 0) + (exact ? 0.5 : 0) };
|
|
713
|
+
}).sort((a, b) => b.score - a.score);
|
|
542
714
|
return {
|
|
543
715
|
content: [{
|
|
544
716
|
type: 'text',
|
|
@@ -552,12 +724,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
552
724
|
if (args.configType && args.configType !== 'other') {
|
|
553
725
|
query = `${args.configType}.xml ${args.query}`;
|
|
554
726
|
}
|
|
555
|
-
const raw =
|
|
556
|
-
const
|
|
727
|
+
const raw = await rustSearchAsync(query, 30);
|
|
728
|
+
const pathBoost = args.configType ? [`${args.configType}.xml`] : ['.xml'];
|
|
729
|
+
let normalized = raw.map(normalizeResult);
|
|
730
|
+
// Prefer XML results when configType is specified, but don't hard-exclude
|
|
731
|
+
if (args.configType) {
|
|
732
|
+
const xmlOnly = normalized.filter(r =>
|
|
733
|
+
r.type === 'xml' || r.path?.endsWith('.xml') || r.path?.includes('.xml')
|
|
734
|
+
);
|
|
735
|
+
if (xmlOnly.length > 0) normalized = xmlOnly;
|
|
736
|
+
}
|
|
737
|
+
const results = rerank(normalized, { fileType: 'xml', pathContains: pathBoost });
|
|
557
738
|
return {
|
|
558
739
|
content: [{
|
|
559
740
|
type: 'text',
|
|
560
|
-
text: formatSearchResults(results)
|
|
741
|
+
text: formatSearchResults(results.slice(0, 10))
|
|
561
742
|
}]
|
|
562
743
|
};
|
|
563
744
|
}
|
|
@@ -566,12 +747,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
566
747
|
let query = args.query;
|
|
567
748
|
if (args.area) query = `${args.area} ${query}`;
|
|
568
749
|
query += ' template phtml';
|
|
569
|
-
const raw =
|
|
570
|
-
const results = raw.map(normalizeResult);
|
|
750
|
+
const raw = await rustSearchAsync(query, 15);
|
|
751
|
+
const results = rerank(raw.map(normalizeResult), { pathContains: ['.phtml'] });
|
|
571
752
|
return {
|
|
572
753
|
content: [{
|
|
573
754
|
type: 'text',
|
|
574
|
-
text: formatSearchResults(results)
|
|
755
|
+
text: formatSearchResults(results.slice(0, 10))
|
|
575
756
|
}]
|
|
576
757
|
};
|
|
577
758
|
}
|
|
@@ -602,45 +783,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
602
783
|
if (args.targetClass) query += ` ${args.targetClass}`;
|
|
603
784
|
if (args.targetMethod) query += ` ${args.targetMethod} before after around`;
|
|
604
785
|
|
|
605
|
-
const raw =
|
|
606
|
-
|
|
786
|
+
const raw = await rustSearchAsync(query, 30);
|
|
787
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
607
788
|
r.isPlugin || r.path?.includes('/Plugin/') || r.path?.includes('di.xml')
|
|
608
789
|
);
|
|
790
|
+
results = rerank(results, { isPlugin: true, pathContains: ['/Plugin/', 'di.xml'] });
|
|
609
791
|
|
|
610
792
|
return {
|
|
611
793
|
content: [{
|
|
612
794
|
type: 'text',
|
|
613
|
-
text: formatSearchResults(results)
|
|
795
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
614
796
|
}]
|
|
615
797
|
};
|
|
616
798
|
}
|
|
617
799
|
|
|
618
800
|
case 'magento_find_observer': {
|
|
619
801
|
const query = `event ${args.eventName} observer`;
|
|
620
|
-
const raw =
|
|
621
|
-
|
|
802
|
+
const raw = await rustSearchAsync(query, 30);
|
|
803
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
622
804
|
r.isObserver || r.path?.includes('/Observer/') || r.path?.includes('events.xml')
|
|
623
805
|
);
|
|
806
|
+
results = rerank(results, { isObserver: true, pathContains: ['events.xml', '/Observer/'] });
|
|
624
807
|
|
|
625
808
|
return {
|
|
626
809
|
content: [{
|
|
627
810
|
type: 'text',
|
|
628
|
-
text:
|
|
811
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
629
812
|
}]
|
|
630
813
|
};
|
|
631
814
|
}
|
|
632
815
|
|
|
633
816
|
case 'magento_find_preference': {
|
|
634
|
-
const query = `preference ${args.interfaceName}`;
|
|
635
|
-
const raw =
|
|
636
|
-
|
|
817
|
+
const query = `preference ${args.interfaceName} di.xml type`;
|
|
818
|
+
const raw = await rustSearchAsync(query, 30);
|
|
819
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
637
820
|
r.path?.includes('di.xml')
|
|
638
821
|
);
|
|
822
|
+
results = rerank(results, { fileType: 'xml', pathContains: ['di.xml'] });
|
|
639
823
|
|
|
640
824
|
return {
|
|
641
825
|
content: [{
|
|
642
826
|
type: 'text',
|
|
643
|
-
text:
|
|
827
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
644
828
|
}]
|
|
645
829
|
};
|
|
646
830
|
}
|
|
@@ -649,26 +833,40 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
649
833
|
let query = `webapi route ${args.query}`;
|
|
650
834
|
if (args.method) query += ` method="${args.method}"`;
|
|
651
835
|
|
|
652
|
-
const raw =
|
|
653
|
-
|
|
836
|
+
const raw = await rustSearchAsync(query, 30);
|
|
837
|
+
let results = rerank(raw.map(normalizeResult), { pathContains: ['webapi.xml'] });
|
|
654
838
|
|
|
655
839
|
return {
|
|
656
840
|
content: [{
|
|
657
841
|
type: 'text',
|
|
658
|
-
text:
|
|
842
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
659
843
|
}]
|
|
660
844
|
};
|
|
661
845
|
}
|
|
662
846
|
|
|
663
847
|
case 'magento_find_controller': {
|
|
664
848
|
const parts = args.route.split('/');
|
|
665
|
-
|
|
849
|
+
// Map route to Magento namespace: catalog/product/view → Catalog Controller Product View
|
|
850
|
+
const namespaceParts = parts.map(p => p.charAt(0).toUpperCase() + p.slice(1));
|
|
851
|
+
const query = `${namespaceParts.join(' ')} controller execute action`;
|
|
666
852
|
|
|
667
|
-
const raw =
|
|
853
|
+
const raw = await rustSearchAsync(query, 30);
|
|
668
854
|
let results = raw.map(normalizeResult).filter(r =>
|
|
669
855
|
r.isController || r.path?.includes('/Controller/')
|
|
670
856
|
);
|
|
671
857
|
|
|
858
|
+
// Boost results whose path matches the route segments
|
|
859
|
+
if (parts.length >= 2) {
|
|
860
|
+
const pathPattern = parts.map(p => p.charAt(0).toUpperCase() + p.slice(1));
|
|
861
|
+
results.sort((a, b) => {
|
|
862
|
+
const aPath = a.path || '';
|
|
863
|
+
const bPath = b.path || '';
|
|
864
|
+
const aMatches = pathPattern.filter(p => aPath.includes(p)).length;
|
|
865
|
+
const bMatches = pathPattern.filter(p => bPath.includes(p)).length;
|
|
866
|
+
return bMatches - aMatches;
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
|
|
672
870
|
if (args.area) {
|
|
673
871
|
results = results.filter(r => r.area === args.area || r.path?.includes(`/${args.area}/`));
|
|
674
872
|
}
|
|
@@ -676,37 +874,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
676
874
|
return {
|
|
677
875
|
content: [{
|
|
678
876
|
type: 'text',
|
|
679
|
-
text:
|
|
877
|
+
text: formatSearchResults(results)
|
|
680
878
|
}]
|
|
681
879
|
};
|
|
682
880
|
}
|
|
683
881
|
|
|
684
882
|
case 'magento_find_block': {
|
|
685
883
|
const query = `block ${args.query}`;
|
|
686
|
-
const raw =
|
|
687
|
-
|
|
884
|
+
const raw = await rustSearchAsync(query, 30);
|
|
885
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
688
886
|
r.isBlock || r.path?.includes('/Block/')
|
|
689
887
|
);
|
|
888
|
+
results = rerank(results, { isBlock: true, pathContains: ['/Block/'] });
|
|
690
889
|
|
|
691
890
|
return {
|
|
692
891
|
content: [{
|
|
693
892
|
type: 'text',
|
|
694
|
-
text: formatSearchResults(results)
|
|
893
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
695
894
|
}]
|
|
696
895
|
};
|
|
697
896
|
}
|
|
698
897
|
|
|
699
898
|
case 'magento_find_cron': {
|
|
700
899
|
const query = `cron job ${args.jobName}`;
|
|
701
|
-
const raw =
|
|
702
|
-
|
|
900
|
+
const raw = await rustSearchAsync(query, 30);
|
|
901
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
703
902
|
r.path?.includes('crontab.xml') || r.path?.includes('/Cron/')
|
|
704
903
|
);
|
|
904
|
+
results = rerank(results, { pathContains: ['crontab.xml', '/Cron/'] });
|
|
705
905
|
|
|
706
906
|
return {
|
|
707
907
|
content: [{
|
|
708
908
|
type: 'text',
|
|
709
|
-
text:
|
|
909
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
710
910
|
}]
|
|
711
911
|
};
|
|
712
912
|
}
|
|
@@ -715,37 +915,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
715
915
|
let query = `graphql ${args.query}`;
|
|
716
916
|
if (args.schemaType) query += ` ${args.schemaType}`;
|
|
717
917
|
|
|
718
|
-
const raw =
|
|
719
|
-
|
|
918
|
+
const raw = await rustSearchAsync(query, 40);
|
|
919
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
720
920
|
r.isResolver || r.path?.includes('/Resolver/') ||
|
|
721
921
|
r.path?.includes('.graphqls') || r.type === 'graphql'
|
|
722
922
|
);
|
|
923
|
+
results = rerank(results, { isResolver: true, pathContains: ['.graphqls', '/Resolver/'] });
|
|
723
924
|
|
|
724
925
|
return {
|
|
725
926
|
content: [{
|
|
726
927
|
type: 'text',
|
|
727
|
-
text:
|
|
928
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
728
929
|
}]
|
|
729
930
|
};
|
|
730
931
|
}
|
|
731
932
|
|
|
732
933
|
case 'magento_find_db_schema': {
|
|
733
|
-
const query = `table ${args.tableName} column
|
|
734
|
-
const raw =
|
|
735
|
-
|
|
934
|
+
const query = `db_schema.xml table ${args.tableName} column declarative schema`;
|
|
935
|
+
const raw = await rustSearchAsync(query, 40);
|
|
936
|
+
let results = raw.map(normalizeResult).filter(r =>
|
|
736
937
|
r.path?.includes('db_schema.xml')
|
|
737
938
|
);
|
|
939
|
+
results = rerank(results, { fileType: 'xml', pathContains: ['db_schema.xml'] });
|
|
738
940
|
|
|
739
941
|
return {
|
|
740
942
|
content: [{
|
|
741
943
|
type: 'text',
|
|
742
|
-
text:
|
|
944
|
+
text: formatSearchResults(results.slice(0, 15))
|
|
743
945
|
}]
|
|
744
946
|
};
|
|
745
947
|
}
|
|
746
948
|
|
|
747
949
|
case 'magento_module_structure': {
|
|
748
|
-
const raw =
|
|
950
|
+
const raw = await rustSearchAsync(args.moduleName, 100);
|
|
749
951
|
const moduleName = args.moduleName.replace('_', '/');
|
|
750
952
|
const results = raw.map(normalizeResult).filter(r =>
|
|
751
953
|
r.path?.includes(moduleName) || r.module?.includes(args.moduleName)
|
|
@@ -914,9 +1116,25 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
914
1116
|
});
|
|
915
1117
|
|
|
916
1118
|
async function main() {
|
|
1119
|
+
// Try to start persistent Rust serve process for fast queries
|
|
1120
|
+
try {
|
|
1121
|
+
startServeProcess();
|
|
1122
|
+
// Give it a moment to load model+index
|
|
1123
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1124
|
+
} catch {
|
|
1125
|
+
// Non-fatal: falls back to execFileSync per query
|
|
1126
|
+
}
|
|
1127
|
+
|
|
917
1128
|
const transport = new StdioServerTransport();
|
|
918
1129
|
await server.connect(transport);
|
|
919
1130
|
console.error('Magector MCP server started (Rust core backend)');
|
|
920
1131
|
}
|
|
921
1132
|
|
|
1133
|
+
// Cleanup on exit
|
|
1134
|
+
process.on('exit', () => {
|
|
1135
|
+
if (serveProcess) {
|
|
1136
|
+
serveProcess.kill();
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
|
|
922
1140
|
main().catch(console.error);
|