magector 2.16.0 → 2.16.1
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 +5 -5
- package/src/mcp-server.js +114 -58
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.16.
|
|
3
|
+
"version": "2.16.1",
|
|
4
4
|
"description": "Semantic code search for Magento 2 — index, search, MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/mcp-server.js",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"ruvector": "^0.1.96"
|
|
34
34
|
},
|
|
35
35
|
"optionalDependencies": {
|
|
36
|
-
"@magector/cli-darwin-arm64": "2.16.
|
|
37
|
-
"@magector/cli-linux-x64": "2.16.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.16.
|
|
39
|
-
"@magector/cli-win32-x64": "2.16.
|
|
36
|
+
"@magector/cli-darwin-arm64": "2.16.1",
|
|
37
|
+
"@magector/cli-linux-x64": "2.16.1",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.16.1",
|
|
39
|
+
"@magector/cli-win32-x64": "2.16.1"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -938,6 +938,10 @@ function tryConnectSocket() {
|
|
|
938
938
|
let globalServeQuery = null;
|
|
939
939
|
|
|
940
940
|
function serveQuery(command, params = {}, timeoutMs = 30000) {
|
|
941
|
+
if (!serveProcess || !serveReady) {
|
|
942
|
+
logToFile('WARN', `serveQuery(${command}): serve process not ready — returning error`);
|
|
943
|
+
return Promise.resolve({ ok: false, error: 'Serve process not ready' });
|
|
944
|
+
}
|
|
941
945
|
return new Promise((resolve, reject) => {
|
|
942
946
|
const id = serveNextId++;
|
|
943
947
|
logToFile('QUERY', `[${id}] → ${command}(${JSON.stringify(params).slice(0, 200)})`);
|
|
@@ -3631,7 +3635,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3631
3635
|
tools: [
|
|
3632
3636
|
{
|
|
3633
3637
|
name: 'magento_search',
|
|
3634
|
-
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.',
|
|
3638
|
+
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. Works best for Magento core and popular vendor modules. For small/custom project-specific modules (e.g. proprietary modules not widely known to the embedding model), use magento_grep instead — semantic search may return 0 results for these. See also: magento_find_class, magento_find_method, magento_find_config for targeted searches.',
|
|
3635
3639
|
inputSchema: {
|
|
3636
3640
|
type: 'object',
|
|
3637
3641
|
properties: {
|
|
@@ -4278,7 +4282,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
4278
4282
|
},
|
|
4279
4283
|
{
|
|
4280
4284
|
name: 'magento_batch',
|
|
4281
|
-
description: 'Execute multiple Magector tool calls in a single request to reduce MCP round-trip overhead. Each query runs in parallel and returns combined results. Use this when you need 2+ independent lookups (e.g., find a class AND its plugins AND its observers in one call instead of three).',
|
|
4285
|
+
description: 'Execute multiple Magector tool calls in a single request to reduce MCP round-trip overhead. Each query runs in parallel and returns combined results. Use this when you need 2+ independent lookups (e.g., find a class AND its plugins AND its observers in one call instead of three). Supported tools: magento_search, magento_find_class, magento_find_method, magento_find_plugin, magento_find_observer, magento_find_config, magento_find_event_flow, magento_find_di_wiring, magento_find_callers, magento_find_preference, magento_find_fieldset, magento_module_structure, magento_trace_dependency, magento_impact_analysis, magento_grep, magento_read, magento_ast_search, magento_find_dataobject_issues, magento_find_null_risks.',
|
|
4282
4286
|
inputSchema: {
|
|
4283
4287
|
type: 'object',
|
|
4284
4288
|
properties: {
|
|
@@ -4391,7 +4395,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
4391
4395
|
},
|
|
4392
4396
|
{
|
|
4393
4397
|
name: 'magento_find_null_risks',
|
|
4394
|
-
description: 'Find method chains without null guards using the pre-built enrichment index. Returns all ->firstMethod()->secondMethod() calls where no null check (=== null, !== null, ?->, ??, isset, is_null) was detected in surrounding code. Requires magento_enrich to have been run first. 100× faster than grep — O(1) SQLite query vs O(n) file scan. Use firstMethod to filter (e.g., "getPayment" finds all ->getPayment()->anything() without null guard). ⚡ For multi-query workflows use magento_batch.',
|
|
4398
|
+
description: 'Find method chains without null guards using the pre-built enrichment index. Returns all ->firstMethod()->secondMethod() calls where no null check (=== null, !== null, ?->, ??, isset, is_null) was detected in surrounding code. Requires magento_enrich to have been run first (magento_index triggers it automatically in the background). 100× faster than grep — O(1) SQLite query vs O(n) file scan. Use firstMethod to filter (e.g., "getPayment" finds all ->getPayment()->anything() without null guard). ⚡ For multi-query workflows use magento_batch.',
|
|
4395
4399
|
inputSchema: {
|
|
4396
4400
|
type: 'object',
|
|
4397
4401
|
properties: {
|
|
@@ -5200,43 +5204,63 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
5200
5204
|
}
|
|
5201
5205
|
|
|
5202
5206
|
case 'magento_module_structure': {
|
|
5203
|
-
const raw = await rustSearchAsync(args.moduleName, 200);
|
|
5204
|
-
// Support both app/code (Magento/Catalog/) and vendor (module-catalog/) paths
|
|
5205
|
-
const modulePath = args.moduleName.replace('_', '/') + '/';
|
|
5206
5207
|
const parts = args.moduleName.split('_');
|
|
5208
|
+
// Support both app/code (Magento/Catalog/) and vendor (magento/module-catalog/) paths
|
|
5209
|
+
const modulePath = args.moduleName.replace('_', '/') + '/';
|
|
5207
5210
|
// Hyphenate camelCase for vendor path: OrderSplit → order-split
|
|
5211
|
+
const vendorDir = parts.length === 2 ? parts[0].toLowerCase() : '';
|
|
5208
5212
|
const vendorPath = parts.length === 2
|
|
5209
5213
|
? `module-${parts[1].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}/`
|
|
5210
5214
|
: '';
|
|
5211
|
-
let results =
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
logToFile('INFO', `module_structure: vector search returned 0 results for "${args.moduleName}" — using filesystem fallback (glob ${vendorPath})`);
|
|
5223
|
-
try {
|
|
5224
|
-
const vendorGlob = `**/${vendorPath}**/*.{php,xml,phtml}`;
|
|
5225
|
-
const files = await glob(vendorGlob, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
5226
|
-
for (const f of files.slice(0, 100)) {
|
|
5227
|
-
const entry = { path: f, score: 0.5 };
|
|
5228
|
-
if (f.includes('/Controller/')) entry.isController = true;
|
|
5229
|
-
if (f.includes('/Model/')) entry.isModel = true;
|
|
5230
|
-
if (f.includes('/Block/')) entry.isBlock = true;
|
|
5231
|
-
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
5232
|
-
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
5233
|
-
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
5234
|
-
// Extract class name from path
|
|
5235
|
-
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
5236
|
-
if (phpMatch) entry.className = phpMatch[1];
|
|
5237
|
-
results.push(entry);
|
|
5215
|
+
let results = [];
|
|
5216
|
+
|
|
5217
|
+
// Primary: filesystem-based (authoritative — avoids mixing cross-references from vector search)
|
|
5218
|
+
if (config.magentoRoot) {
|
|
5219
|
+
const fsGlobs = [];
|
|
5220
|
+
if (parts.length === 2) {
|
|
5221
|
+
// app/code/{Vendor}/{Module}/
|
|
5222
|
+
fsGlobs.push(`app/code/${parts[0]}/${parts[1]}/**/*.{php,xml,phtml}`);
|
|
5223
|
+
// vendor/{vendor-lower}/{module-lower}/ — vendor-specific to avoid false positives
|
|
5224
|
+
if (vendorDir && vendorPath) {
|
|
5225
|
+
fsGlobs.push(`vendor/${vendorDir}/${vendorPath}**/*.{php,xml,phtml}`);
|
|
5238
5226
|
}
|
|
5239
|
-
}
|
|
5227
|
+
}
|
|
5228
|
+
for (const globPattern of fsGlobs) {
|
|
5229
|
+
try {
|
|
5230
|
+
const files = await glob(globPattern, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
5231
|
+
if (files.length > 0) {
|
|
5232
|
+
logToFile('INFO', `module_structure: filesystem found ${files.length} files for "${args.moduleName}" (${globPattern})`);
|
|
5233
|
+
for (const f of files.slice(0, 100)) {
|
|
5234
|
+
const entry = { path: f, score: 1.0 };
|
|
5235
|
+
if (f.includes('/Controller/')) entry.isController = true;
|
|
5236
|
+
if (f.includes('/Model/')) entry.isModel = true;
|
|
5237
|
+
if (f.includes('/Block/')) entry.isBlock = true;
|
|
5238
|
+
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
5239
|
+
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
5240
|
+
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
5241
|
+
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
5242
|
+
if (phpMatch) entry.className = phpMatch[1];
|
|
5243
|
+
results.push(entry);
|
|
5244
|
+
}
|
|
5245
|
+
break; // Found in one location, stop
|
|
5246
|
+
}
|
|
5247
|
+
} catch {}
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
|
|
5251
|
+
// Fallback: vector search with strict path/module filtering (only if filesystem found nothing)
|
|
5252
|
+
if (results.length === 0) {
|
|
5253
|
+
logToFile('INFO', `module_structure: filesystem found 0 files for "${args.moduleName}" — falling back to vector search`);
|
|
5254
|
+
const raw = await rustSearchAsync(args.moduleName, 200);
|
|
5255
|
+
results = raw.map(normalizeResult).filter(r => {
|
|
5256
|
+
const p = r.path || '';
|
|
5257
|
+
const mod = r.module || '';
|
|
5258
|
+
// Exact module match or directory-level path match (trailing slash prevents Catalog matching CatalogRule)
|
|
5259
|
+
return mod === args.moduleName ||
|
|
5260
|
+
p.includes(modulePath) ||
|
|
5261
|
+
// Vendor-specific path check to avoid matching other vendors' same-named modules
|
|
5262
|
+
(vendorDir && vendorPath && p.toLowerCase().includes(`${vendorDir}/${vendorPath}`));
|
|
5263
|
+
});
|
|
5240
5264
|
}
|
|
5241
5265
|
|
|
5242
5266
|
const structure = {
|
|
@@ -6182,6 +6206,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
6182
6206
|
}
|
|
6183
6207
|
break;
|
|
6184
6208
|
}
|
|
6209
|
+
case 'magento_find_config': {
|
|
6210
|
+
let cfgQuery = a.query;
|
|
6211
|
+
if (a.configType && a.configType !== 'other') cfgQuery = `${a.configType}.xml xml config ${a.query}`;
|
|
6212
|
+
const cfgRaw = await rustSearchAsync(cfgQuery, 100);
|
|
6213
|
+
let cfgRes = cfgRaw.map(normalizeResult).filter(r =>
|
|
6214
|
+
r.type === 'xml' || r.path?.endsWith('.xml') || r.path?.includes('.xml')
|
|
6215
|
+
);
|
|
6216
|
+
if (a.configType && a.configType !== 'other') {
|
|
6217
|
+
const cfgTypeFile = `${a.configType}.xml`;
|
|
6218
|
+
const cfgTyped = cfgRes.filter(r => r.path?.includes(cfgTypeFile));
|
|
6219
|
+
if (cfgTyped.length >= 3) cfgRes = cfgTyped;
|
|
6220
|
+
}
|
|
6221
|
+
text = formatSearchResults(cfgRes.slice(0, 10));
|
|
6222
|
+
break;
|
|
6223
|
+
}
|
|
6185
6224
|
case 'magento_trace_dependency': {
|
|
6186
6225
|
const dep = await traceDependency(a.className, a.direction || 'both');
|
|
6187
6226
|
text = `Preferences: ${dep.preferences.length}, Plugins: ${dep.plugins.length}, VirtualTypes: ${dep.virtualTypes.length}, Args: ${dep.argumentOverrides.length}\n`;
|
|
@@ -6271,35 +6310,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
6271
6310
|
break;
|
|
6272
6311
|
}
|
|
6273
6312
|
case 'magento_module_structure': {
|
|
6274
|
-
const raw = await rustSearchAsync(a.moduleName, 200);
|
|
6275
|
-
const modulePath = a.moduleName.replace('_', '/') + '/';
|
|
6276
6313
|
const mParts = a.moduleName.split('_');
|
|
6277
|
-
|
|
6314
|
+
const modulePath = a.moduleName.replace('_', '/') + '/';
|
|
6315
|
+
const mVendorDir = mParts.length === 2 ? mParts[0].toLowerCase() : '';
|
|
6278
6316
|
const vendorPath = mParts.length === 2
|
|
6279
6317
|
? `module-${mParts[1].replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}/`
|
|
6280
6318
|
: '';
|
|
6281
|
-
let res =
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
const vendorGlob = `**/${vendorPath}**/*.{php,xml,phtml}`;
|
|
6290
|
-
const files = await glob(vendorGlob, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
6291
|
-
for (const f of files.slice(0, 100)) {
|
|
6292
|
-
const entry = { path: f, score: 0.5 };
|
|
6293
|
-
if (f.includes('/Controller/')) entry.isController = true;
|
|
6294
|
-
if (f.includes('/Model/')) entry.isModel = true;
|
|
6295
|
-
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
6296
|
-
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
6297
|
-
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
6298
|
-
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
6299
|
-
if (phpMatch) entry.className = phpMatch[1];
|
|
6300
|
-
res.push(entry);
|
|
6319
|
+
let res = [];
|
|
6320
|
+
// Primary: filesystem-based (avoids mixing cross-references)
|
|
6321
|
+
if (config.magentoRoot) {
|
|
6322
|
+
const msGlobs = [];
|
|
6323
|
+
if (mParts.length === 2) {
|
|
6324
|
+
msGlobs.push(`app/code/${mParts[0]}/${mParts[1]}/**/*.{php,xml,phtml}`);
|
|
6325
|
+
if (mVendorDir && vendorPath) {
|
|
6326
|
+
msGlobs.push(`vendor/${mVendorDir}/${vendorPath}**/*.{php,xml,phtml}`);
|
|
6301
6327
|
}
|
|
6302
|
-
}
|
|
6328
|
+
}
|
|
6329
|
+
for (const gp of msGlobs) {
|
|
6330
|
+
try {
|
|
6331
|
+
const files = await glob(gp, { cwd: config.magentoRoot, absolute: false, nodir: true });
|
|
6332
|
+
if (files.length > 0) {
|
|
6333
|
+
for (const f of files.slice(0, 100)) {
|
|
6334
|
+
const entry = { path: f, score: 1.0 };
|
|
6335
|
+
if (f.includes('/Controller/')) entry.isController = true;
|
|
6336
|
+
if (f.includes('/Model/')) entry.isModel = true;
|
|
6337
|
+
if (f.includes('/Plugin/')) entry.isPlugin = true;
|
|
6338
|
+
if (f.includes('/Observer/')) entry.isObserver = true;
|
|
6339
|
+
if (f.endsWith('.xml')) entry.type = 'xml';
|
|
6340
|
+
const phpMatch = f.match(/\/([A-Z]\w+)\.php$/);
|
|
6341
|
+
if (phpMatch) entry.className = phpMatch[1];
|
|
6342
|
+
res.push(entry);
|
|
6343
|
+
}
|
|
6344
|
+
break;
|
|
6345
|
+
}
|
|
6346
|
+
} catch {}
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6349
|
+
// Fallback: vector search with vendor-specific path filtering
|
|
6350
|
+
if (res.length === 0) {
|
|
6351
|
+
const raw = await rustSearchAsync(a.moduleName, 200);
|
|
6352
|
+
res = raw.map(normalizeResult).filter(r => {
|
|
6353
|
+
const p = r.path || '';
|
|
6354
|
+
const mod = r.module || '';
|
|
6355
|
+
return mod === a.moduleName ||
|
|
6356
|
+
p.includes(modulePath) ||
|
|
6357
|
+
(mVendorDir && vendorPath && p.toLowerCase().includes(`${mVendorDir}/${vendorPath}`));
|
|
6358
|
+
});
|
|
6303
6359
|
}
|
|
6304
6360
|
text = `Module: ${a.moduleName} (${res.length} files)\n`;
|
|
6305
6361
|
const cats = { controllers: '/Controller/', models: '/Model/', plugins: '/Plugin/', observers: '/Observer/', api: '/Api/' };
|