magector 2.3.1 → 2.4.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 +202 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magector",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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.
|
|
37
|
-
"@magector/cli-linux-x64": "2.
|
|
38
|
-
"@magector/cli-linux-arm64": "2.
|
|
39
|
-
"@magector/cli-win32-x64": "2.
|
|
36
|
+
"@magector/cli-darwin-arm64": "2.4.1",
|
|
37
|
+
"@magector/cli-linux-x64": "2.4.1",
|
|
38
|
+
"@magector/cli-linux-arm64": "2.4.1",
|
|
39
|
+
"@magector/cli-win32-x64": "2.4.1"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"magento",
|
package/src/mcp-server.js
CHANGED
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
} from 'ruvector/dist/analysis/complexity.js';
|
|
33
33
|
import { resolveBinary } from './binary.js';
|
|
34
34
|
import { resolveModels } from './model.js';
|
|
35
|
+
import { createRequire } from 'module';
|
|
36
|
+
const __pkg = createRequire(import.meta.url)('../package.json');
|
|
35
37
|
|
|
36
38
|
const config = {
|
|
37
39
|
dbPath: process.env.MAGECTOR_DB || './.magector/index.db',
|
|
@@ -2816,7 +2818,7 @@ async function traceCallChain(startClass, startMethod, maxDepth = 3) {
|
|
|
2816
2818
|
const server = new Server(
|
|
2817
2819
|
{
|
|
2818
2820
|
name: 'magector',
|
|
2819
|
-
version:
|
|
2821
|
+
version: __pkg.version
|
|
2820
2822
|
},
|
|
2821
2823
|
{
|
|
2822
2824
|
capabilities: {
|
|
@@ -3086,7 +3088,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3086
3088
|
},
|
|
3087
3089
|
{
|
|
3088
3090
|
name: 'magento_find_db_schema',
|
|
3089
|
-
description: 'Find database table definitions, columns, indexes, and constraints declared in db_schema.xml (Magento declarative schema). See also:
|
|
3091
|
+
description: 'Find database table definitions, columns, indexes, and constraints declared in db_schema.xml (Magento declarative schema) AND legacy Setup scripts (InstallSchema, UpgradeSchema). Covers both modern declarative schema and legacy $setup->newTable() / addColumn() table definitions. See also: magento_find_trigger (DB triggers), magento_find_table_usage (cross-module table references).',
|
|
3090
3092
|
inputSchema: {
|
|
3091
3093
|
type: 'object',
|
|
3092
3094
|
properties: {
|
|
@@ -3098,6 +3100,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
3098
3100
|
required: ['tableName']
|
|
3099
3101
|
}
|
|
3100
3102
|
},
|
|
3103
|
+
{
|
|
3104
|
+
name: 'magento_find_trigger',
|
|
3105
|
+
description: 'Find MySQL database trigger definitions in Magento Setup scripts. Detects triggers created via TriggerFactory (setName, setTable, setEvent, setTime, addStatement, createTrigger). Returns trigger name, target table, event type (INSERT/UPDATE/DELETE), timing (BEFORE/AFTER), and SQL statements. Use when investigating DB-level automation, trigger chains, or performance issues caused by cascading triggers.',
|
|
3106
|
+
inputSchema: {
|
|
3107
|
+
type: 'object',
|
|
3108
|
+
properties: {
|
|
3109
|
+
triggerName: {
|
|
3110
|
+
type: 'string',
|
|
3111
|
+
description: 'Optional trigger name or pattern to search for. If omitted, finds all triggers in the codebase.'
|
|
3112
|
+
},
|
|
3113
|
+
tableName: {
|
|
3114
|
+
type: 'string',
|
|
3115
|
+
description: 'Optional table name to find triggers targeting this table.'
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
},
|
|
3120
|
+
{
|
|
3121
|
+
name: 'magento_find_table_usage',
|
|
3122
|
+
description: 'Find all code that references a database table — across db_schema.xml, Setup scripts (InstallSchema/UpgradeSchema), raw SQL (Zend_Db_Expr, $connection->query), getTable() calls, and resource model definitions. Builds a cross-module dependency map showing who reads/writes/creates a given table. Essential for impact analysis of schema changes.',
|
|
3123
|
+
inputSchema: {
|
|
3124
|
+
type: 'object',
|
|
3125
|
+
properties: {
|
|
3126
|
+
tableName: {
|
|
3127
|
+
type: 'string',
|
|
3128
|
+
description: 'Database table name to find all references for. Examples: "salesrule_ordered", "catalog_product_entity", "quote_item"'
|
|
3129
|
+
}
|
|
3130
|
+
},
|
|
3131
|
+
required: ['tableName']
|
|
3132
|
+
}
|
|
3133
|
+
},
|
|
3101
3134
|
{
|
|
3102
3135
|
name: 'magento_module_structure',
|
|
3103
3136
|
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.',
|
|
@@ -3622,30 +3655,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3622
3655
|
if (args.targetClass) {
|
|
3623
3656
|
const fpRoot = config.magentoRoot;
|
|
3624
3657
|
const diFiles = await glob('**/etc/**/di.xml', { cwd: fpRoot, absolute: true, nodir: true });
|
|
3625
|
-
|
|
3658
|
+
// Normalize target class for matching (both \ and \\)
|
|
3659
|
+
const normalizedTarget = args.targetClass.replace(/\\\\/g, '\\');
|
|
3626
3660
|
for (const diFile of diFiles) {
|
|
3627
3661
|
let content;
|
|
3628
3662
|
try { content = readFileSync(diFile, 'utf-8'); } catch { continue; }
|
|
3629
|
-
if (!content.includes(
|
|
3663
|
+
if (!content.includes(normalizedTarget)) continue;
|
|
3630
3664
|
const relPath = diFile.replace(fpRoot + '/', '');
|
|
3631
3665
|
// Find plugin registrations for this target
|
|
3632
3666
|
const typeBlockRegex = /<type\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/type>/g;
|
|
3633
3667
|
let tm;
|
|
3634
3668
|
while ((tm = typeBlockRegex.exec(content)) !== null) {
|
|
3635
|
-
const typeName = tm[1];
|
|
3636
|
-
if (
|
|
3669
|
+
const typeName = tm[1].replace(/\\\\/g, '\\');
|
|
3670
|
+
if (typeName !== normalizedTarget) continue;
|
|
3637
3671
|
const block = tm[2];
|
|
3638
|
-
const pluginRegex = /<plugin\s+
|
|
3672
|
+
const pluginRegex = /<plugin\s+([^/>]*)\/?>/g;
|
|
3639
3673
|
let pm;
|
|
3640
3674
|
while ((pm = pluginRegex.exec(block)) !== null) {
|
|
3675
|
+
const attrs = {};
|
|
3676
|
+
const localAttrRe = /(\w+)="([^"]*)"/g;
|
|
3677
|
+
let am;
|
|
3678
|
+
while ((am = localAttrRe.exec(pm[1])) !== null) {
|
|
3679
|
+
attrs[am[1]] = am[2];
|
|
3680
|
+
}
|
|
3641
3681
|
let area = 'global';
|
|
3642
3682
|
if (relPath.includes('/etc/adminhtml/')) area = 'adminhtml';
|
|
3643
3683
|
else if (relPath.includes('/etc/frontend/')) area = 'frontend';
|
|
3644
3684
|
else if (relPath.includes('/etc/graphql/')) area = 'graphql';
|
|
3645
3685
|
diRegistrations.push({
|
|
3646
3686
|
target: typeName,
|
|
3647
|
-
pluginName:
|
|
3648
|
-
pluginClass:
|
|
3687
|
+
pluginName: attrs.name || '',
|
|
3688
|
+
pluginClass: attrs.type || '',
|
|
3689
|
+
disabled: attrs.disabled === 'true',
|
|
3690
|
+
sortOrder: attrs.sortOrder || null,
|
|
3649
3691
|
area,
|
|
3650
3692
|
file: relPath
|
|
3651
3693
|
});
|
|
@@ -3658,7 +3700,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3658
3700
|
if (diRegistrations.length > 0) {
|
|
3659
3701
|
text += `\n\n### DI Plugin Registrations for ${args.targetClass} (${diRegistrations.length})\n`;
|
|
3660
3702
|
for (const reg of diRegistrations) {
|
|
3661
|
-
|
|
3703
|
+
const disabledTag = reg.disabled ? ' **[DISABLED]**' : '';
|
|
3704
|
+
const sortTag = reg.sortOrder ? ` (sortOrder: ${reg.sortOrder})` : '';
|
|
3705
|
+
text += `- **${reg.pluginName}** → \`${reg.pluginClass}\` [${reg.area}]${sortTag}${disabledTag} (${reg.file})\n`;
|
|
3662
3706
|
}
|
|
3663
3707
|
}
|
|
3664
3708
|
|
|
@@ -3807,17 +3851,160 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3807
3851
|
}
|
|
3808
3852
|
|
|
3809
3853
|
case 'magento_find_db_schema': {
|
|
3810
|
-
|
|
3811
|
-
const
|
|
3812
|
-
|
|
3854
|
+
// Search both declarative schema (db_schema.xml) and legacy Setup scripts
|
|
3855
|
+
const declQuery = `db_schema.xml table ${args.tableName} column declarative schema`;
|
|
3856
|
+
const legacyQuery = `create_table ${args.tableName} legacy_schema table_created ${args.tableName} setup install schema newTable addColumn`;
|
|
3857
|
+
const [declRaw, legacyRaw] = await Promise.all([
|
|
3858
|
+
rustSearchAsync(declQuery, 40),
|
|
3859
|
+
rustSearchAsync(legacyQuery, 30)
|
|
3860
|
+
]);
|
|
3861
|
+
|
|
3862
|
+
let declResults = declRaw.map(normalizeResult).filter(r =>
|
|
3813
3863
|
r.path?.includes('db_schema.xml')
|
|
3814
3864
|
);
|
|
3815
|
-
|
|
3865
|
+
declResults = rerank(declResults, { fileType: 'xml', pathContains: ['db_schema.xml'] });
|
|
3866
|
+
|
|
3867
|
+
let legacyResults = legacyRaw.map(normalizeResult).filter(r => {
|
|
3868
|
+
const p = r.path || '';
|
|
3869
|
+
return (p.includes('/Setup/') || p.includes('InstallSchema') ||
|
|
3870
|
+
p.includes('UpgradeSchema') || p.includes('/Patch/')) &&
|
|
3871
|
+
(r.snippet?.toLowerCase().includes(args.tableName.toLowerCase()) ||
|
|
3872
|
+
r.searchText?.toLowerCase().includes(args.tableName.toLowerCase()));
|
|
3873
|
+
});
|
|
3874
|
+
|
|
3875
|
+
// Deduplicate by path
|
|
3876
|
+
const seen = new Set(declResults.map(r => r.path));
|
|
3877
|
+
for (const r of legacyResults) {
|
|
3878
|
+
if (!seen.has(r.path)) {
|
|
3879
|
+
seen.add(r.path);
|
|
3880
|
+
declResults.push(r);
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
// Add section headers
|
|
3885
|
+
let output = '';
|
|
3886
|
+
const xmlResults = declResults.filter(r => r.path?.includes('db_schema.xml'));
|
|
3887
|
+
const setupResults = declResults.filter(r => !r.path?.includes('db_schema.xml'));
|
|
3888
|
+
|
|
3889
|
+
if (xmlResults.length > 0) {
|
|
3890
|
+
output += formatSearchResults(xmlResults.slice(0, 10));
|
|
3891
|
+
}
|
|
3892
|
+
if (setupResults.length > 0) {
|
|
3893
|
+
output += `\n\n### Legacy Setup Scripts (InstallSchema/UpgradeSchema)\n`;
|
|
3894
|
+
output += formatSearchResults(setupResults.slice(0, 10));
|
|
3895
|
+
}
|
|
3816
3896
|
|
|
3817
3897
|
return {
|
|
3818
3898
|
content: [{
|
|
3819
3899
|
type: 'text',
|
|
3820
|
-
text: formatSearchResults(
|
|
3900
|
+
text: output || formatSearchResults([])
|
|
3901
|
+
}]
|
|
3902
|
+
};
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3905
|
+
case 'magento_find_trigger': {
|
|
3906
|
+
// Search for DB trigger definitions in Setup scripts
|
|
3907
|
+
const triggerTerm = args.triggerName || args.tableName || '';
|
|
3908
|
+
const query = `db_trigger create_trigger sql_trigger TriggerFactory createTrigger setName setEvent ${triggerTerm} setup database_trigger trigger`;
|
|
3909
|
+
const raw = await rustSearchAsync(query, 50);
|
|
3910
|
+
let results = raw.map(normalizeResult).filter(r => {
|
|
3911
|
+
const p = r.path || '';
|
|
3912
|
+
const s = (r.searchText || '') + ' ' + (r.snippet || '');
|
|
3913
|
+
// Must be a Setup file that contains trigger-related terms
|
|
3914
|
+
return (p.includes('/Setup/') || p.includes('InstallSchema') ||
|
|
3915
|
+
p.includes('UpgradeSchema') || p.includes('/Patch/')) &&
|
|
3916
|
+
(s.toLowerCase().includes('trigger') || s.includes('db_trigger') || s.includes('create_trigger'));
|
|
3917
|
+
});
|
|
3918
|
+
|
|
3919
|
+
// If searching for specific trigger/table, filter further
|
|
3920
|
+
if (triggerTerm) {
|
|
3921
|
+
const term = triggerTerm.toLowerCase();
|
|
3922
|
+
const filtered = results.filter(r => {
|
|
3923
|
+
const s = ((r.searchText || '') + ' ' + (r.snippet || '')).toLowerCase();
|
|
3924
|
+
return s.includes(term);
|
|
3925
|
+
});
|
|
3926
|
+
if (filtered.length > 0) results = filtered;
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
results = rerank(results, { pathContains: ['/Setup/', 'UpgradeSchema', 'InstallSchema'] });
|
|
3930
|
+
|
|
3931
|
+
return {
|
|
3932
|
+
content: [{
|
|
3933
|
+
type: 'text',
|
|
3934
|
+
text: results.length > 0
|
|
3935
|
+
? `### DB Trigger Definitions\n\n` + formatSearchResults(results.slice(0, 15)) +
|
|
3936
|
+
`\n\n**Tip:** Read the matched Setup files to see trigger names, target tables, events (INSERT/UPDATE/DELETE), timing (BEFORE/AFTER), and SQL statements.`
|
|
3937
|
+
: `No database triggers found${triggerTerm ? ` matching "${triggerTerm}"` : ''}. Triggers are defined in Setup scripts using TriggerFactory.`
|
|
3938
|
+
}]
|
|
3939
|
+
};
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
case 'magento_find_table_usage': {
|
|
3943
|
+
const table = args.tableName;
|
|
3944
|
+
// Multi-query strategy: search declarative schema, setup scripts, and inline SQL references
|
|
3945
|
+
const queries = [
|
|
3946
|
+
`table ${table} db_schema.xml declarative schema column`,
|
|
3947
|
+
`sql_table ${table} table_reference ${table} Zend_Db_Expr getTable`,
|
|
3948
|
+
`create_table ${table} legacy_schema setup trigger ${table}`,
|
|
3949
|
+
`${table.replace(/_/g, ' ')} resource model collection`,
|
|
3950
|
+
];
|
|
3951
|
+
|
|
3952
|
+
const rawResults = await Promise.all(queries.map(q => rustSearchAsync(q, 25)));
|
|
3953
|
+
const allResults = rawResults.flat().map(normalizeResult);
|
|
3954
|
+
|
|
3955
|
+
// Deduplicate by path
|
|
3956
|
+
const pathMap = new Map();
|
|
3957
|
+
for (const r of allResults) {
|
|
3958
|
+
if (!r.path) continue;
|
|
3959
|
+
const s = ((r.searchText || '') + ' ' + (r.snippet || '')).toLowerCase();
|
|
3960
|
+
if (s.includes(table.toLowerCase()) || r.path.includes('db_schema.xml')) {
|
|
3961
|
+
if (!pathMap.has(r.path) || (r.score || 0) > (pathMap.get(r.path).score || 0)) {
|
|
3962
|
+
pathMap.set(r.path, r);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
const unique = Array.from(pathMap.values());
|
|
3967
|
+
|
|
3968
|
+
// Categorize results
|
|
3969
|
+
const categories = {
|
|
3970
|
+
'Declarative Schema (db_schema.xml)': unique.filter(r => r.path?.includes('db_schema.xml')),
|
|
3971
|
+
'Setup Scripts (InstallSchema/UpgradeSchema/Patch)': unique.filter(r => {
|
|
3972
|
+
const p = r.path || '';
|
|
3973
|
+
return !p.includes('db_schema.xml') &&
|
|
3974
|
+
(p.includes('/Setup/') || p.includes('InstallSchema') ||
|
|
3975
|
+
p.includes('UpgradeSchema') || p.includes('/Patch/'));
|
|
3976
|
+
}),
|
|
3977
|
+
'PHP Code (raw SQL, getTable, Zend_Db_Expr)': unique.filter(r => {
|
|
3978
|
+
const p = r.path || '';
|
|
3979
|
+
return p.endsWith('.php') && !p.includes('db_schema.xml') &&
|
|
3980
|
+
!p.includes('/Setup/') && !p.includes('InstallSchema') &&
|
|
3981
|
+
!p.includes('UpgradeSchema') && !p.includes('/Patch/');
|
|
3982
|
+
}),
|
|
3983
|
+
'XML Config': unique.filter(r => {
|
|
3984
|
+
const p = r.path || '';
|
|
3985
|
+
return p.endsWith('.xml') && !p.includes('db_schema.xml');
|
|
3986
|
+
}),
|
|
3987
|
+
};
|
|
3988
|
+
|
|
3989
|
+
let output = `### Table Usage: \`${table}\`\n\n`;
|
|
3990
|
+
output += `Found ${unique.length} file(s) referencing this table.\n\n`;
|
|
3991
|
+
|
|
3992
|
+
for (const [category, items] of Object.entries(categories)) {
|
|
3993
|
+
if (items.length > 0) {
|
|
3994
|
+
output += `#### ${category} (${items.length})\n`;
|
|
3995
|
+
output += formatSearchResults(items.slice(0, 8));
|
|
3996
|
+
output += '\n';
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
if (unique.length === 0) {
|
|
4001
|
+
output += `No references found for table "${table}". Try a broader search with magento_search.`;
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
return {
|
|
4005
|
+
content: [{
|
|
4006
|
+
type: 'text',
|
|
4007
|
+
text: output
|
|
3821
4008
|
}]
|
|
3822
4009
|
};
|
|
3823
4010
|
}
|