Magector
MCP server for Magento 2 and Adobe Commerce: a semantic index of the codebase plus structural tools that read the Magento configuration.
Magector is a Model Context Protocol (MCP) server for Magento 2 and Adobe Commerce codebases. It builds a semantic vector index of the project's PHP, XML, JS, PHTML and GraphQL files (and the module READMEs under app/code) and exposes 48 tools that AI assistants use to search and navigate the code. A query such as "how are checkout totals calculated?" returns ranked files, each annotated with the Magento component type detected for it (plugin, observer, controller, block, layout XML and others). Structural tools answer questions such as "which plugins intercept this method" from di.xml, events.xml, webapi.xml and the other configuration files, without the vector index.
Why Magector
A Magento 2 or Adobe Commerce project consists of PHP, XML, JS, PHTML and GraphQL files spread across hundreds of modules. The codebase relies heavily on indirection: plugins intercept methods defined in other modules, observers react to events dispatched elsewhere, di.xml rewires interfaces to concrete classes, and layout XML stitches blocks and templates together.
Magector provides two kinds of answers:
-
Semantic vector index — every indexed file is embedded into a 384-dimensional space with the ONNX model bge-small-en-v1.5, and a query is matched to the nearest vectors (combined with keyword reranking), not to literal strings.
-
Magento pattern detection — the indexer classifies files by path and by their PHP AST (controllers, plugins, observers, blocks, models, repositories, GraphQL resolvers, cron, setup patches, API interfaces, configuration XML types and more). Every search result carries that classification.
The structural tools (DI, events, Web API, GraphQL, cron, DB schema and others) read the configuration files directly; see How exact are the results?.
Features
- Semantic search -- natural-language queries matched against embedding vectors of the indexed files
- Hybrid search -- combines semantic vector similarity with keyword re-ranking
- Structured JSON output -- semantic results include file path, class name, methods list, role badges and, for the top results, a content snippet
- Persistent serve mode -- a long-running
magector-core serveprocess keeps the ONNX model and the vector index in memory, so a query does not load them - Incremental re-indexing -- a background file watcher re-indexes changed files without a restart (tombstone + compact strategy)
- ONNX embeddings -- 384-dim embeddings with bge-small-en-v1.5 via ONNX Runtime (CPU)
- Magento-aware -- path- and AST-based detection of controllers, plugins, observers, blocks, resolvers, repositories and other Magento file types
- Adobe Commerce -- Adobe Commerce modules are indexed and parsed like Open Source modules (the verification runs in this repo used Magento Open Source and Mage-OS)
- AST-based -- tree-sitter parsing for PHP and JavaScript extracts classes, methods, namespaces and inheritance
- Cross-tool hints -- tool descriptions include keywords and "See also" references to related tools
- SONA feedback learning -- learns from MCP tool call sequences (e.g. search → find_plugin) and boosts matching result types for similar queries; an evaluation found no measurable ranking change. A rank-2 MicroLoRA adapter (1536 parameters) with EWC regularisation is present in the code but is not trained online (see SONA Feedback Learning)
- Diff analysis -- risk scoring and change classification for a git commit or the staged changes
- Complexity analysis -- cyclomatic complexity, function count and line count per file
- LLM description enrichment -- optional natural-language descriptions of di.xml files generated with Claude, stored in SQLite and prepended to the embedding text so they influence vector ranking
- MCP server -- 48 tools for Claude Code, Cursor and other MCP clients
- Split architecture -- the Rust core does indexing, embedding and vector search; the Node.js layer provides the CLI and the MCP server
Architecture
flowchart LR
subgraph node ["Node.js Layer"]
direction TB
G["CLI<br/>init · index · search · describe · snapshot"]
E["MCP Server<br/>48 tools · query cache"]
F["Persistent Serve Process"]
E --> F
end
G -->|"one-shot commands"| rust
F -->|"stdin/stdout JSON"| rust
subgraph rust ["Rust Core"]
direction TB
A["AST Parser<br/>PHP · JS (tree-sitter)"]
B["Pattern Detection<br/>path + AST rules"]
B2["Description Enrichment<br/>LLM di.xml summaries (optional)"]
C["ONNX Embedder<br/>bge-small-en-v1.5 · 384d"]
D["Vector Search<br/>exact ≤300k · HNSW above · hybrid rerank · SONA"]
A --> B --> B2 --> C --> D
end
style rust fill:#f4a460,color:#000
style node fill:#68b684,color:#000
Indexing Pipeline
flowchart LR
A["Source File"] --> B["AST Parser"]
B --> C["Pattern Detection"]
C --> D["Text Enrichment"]
D --> D2{"Descriptions DB?"}
D2 -->|Yes| D3["Prepend LLM Description"]
D2 -->|No| E["ONNX Embedding"]
D3 --> E
E --> F[("Vector Index<br/>index.db")]
A --> G["Metadata"] --> F
Search Pipeline
flowchart LR
Q["Query"] --> E1["Synonym Expansion<br/>(MCP server)"]
E1 --> E2["ONNX Embedding"]
E2 --> H["Vector Search"]
H --> R["Hybrid Reranking"]
R --> SA["SONA Adjustment"]
SA --> J["Structured JSON"]
Components
Security
Magector operates on source code indexed from potentially untrusted vendor/ dependencies and is driven by an LLM that may be manipulated via prompt injection in indexed comments, docblocks or markdown. The following hardening applies:
Indexed markdown
The only markdown in the index is the project's own module READMEs, app/code/<Vendor>/<Module>/README.md: written next to that code and trusted like its comments. Markdown from vendor/ packages and anywhere else is never indexed, so magento_search does not hand third-party markdown to the LLM. Code in vendor/ is indexed, so its comments and docblocks are in the index and can appear in result snippets.
Path arguments
Tools that read files under a path argument route it through the safePath() / safeRelPath() helpers in src/mcp-server.js:
safePath():magento_read(also insidemagento_batch) andmagento_complexitysafeRelPath():magento_grep,magento_ast_searchandmagento_find_dataobject_issues(also insidemagento_batch), andmagento_validate_config
These helpers:
- Resolve the argument against
MAGENTO_ROOTwithpath.resolve(), which normalizes... - Reject any resolved path that does not lie inside
MAGENTO_ROOT.
This stops a hostile vendor/ comment from instructing the LLM to e.g. magento_read ../../home/user/.ssh/id_rsa. The check is lexical: symlinks are not resolved, so a symlink inside MAGENTO_ROOT that points outside it passes the check and its target is read.
Other path-related rules:
magento_complexityalso rejects amodulethat is not of the formVendor_Module.magento_indexis not confined toMAGENTO_ROOT: itspathis resolved against the server's working directory and must be an existing directory; anything else is refused. The index is written to the configuredMAGECTOR_DB, replacing the current project's index.
The confinement only stops reads outside MAGENTO_ROOT. Everything inside it is readable by magento_read and magento_grep, including app/etc/env.php (database password, crypt key). Do not run the server against a root that holds production secrets.
Git refs in magento_analyze_diff
commitHash must be a plain git ref: it starts with a letter or digit, contains only letters, digits and _ . / ^ ~ -, optionally ends in a reflog selector @{n}, and contains no ... Anything else is rejected. git runs without a shell, as git -C $MAGENTO_ROOT … with an argument array. An unknown ref, or a root that is not a git repository, returns git's error instead of an empty result.
Auto-update
src/update.js reads the version of the latest dist-tag from https://registry.npmjs.org/magector/latest (cached for 1 h in .magector/version-check.json when the current directory has .magector/, else in ~/.magector/version-check.json) and re-runs the CLI with that version when it is newer:
- A semver-strict
isSafeVersion()validator rejects any version string that does not matchX.Y.Z/X.Y.Z-prerelease. - On POSIX the re-exec passes argv as an array with no shell.
- On Windows, where
npxis a.cmdshim, the re-exec needs a shell; every argument is first checked against an allow-list, and the update is skipped (the current version runs) when an argument contains characterscmd.exewould interpret (spaces,&,|, quotes). - Fails closed: when the version or an argument is rejected, the update is skipped.
- It never runs for
mcporhelp, and not whenMAGECTOR_NO_UPDATEis set. The MCP config written byinitstarts the server asnpx -y magector@latest mcp, so each MCP start can still fetch a newer version; pinmagector@<version>in.mcp.json/~/.cursor/mcp.jsonto keep one version.
Unix socket permissions
The serve-proxy Unix socket at .magector/serve.sock is set to mode 0600 right after listen(), so other local accounts cannot connect and query the index (which would leak indexed source snippets). If the chmod fails (e.g. on a platform without it), a warning is logged to .magector/magector.log.
Reporting vulnerabilities
Report security issues privately through GitHub's private vulnerability reporting (the repository's Security tab → Report a vulnerability), not in a public issue. Do not include reproducers that leak source contents from private codebases.
Quick Start
Prerequisites
1. Initialize in Your Project
cd /path/to/your/magento2 # or Adobe Commerce project
npx magector init
This command handles the setup:
flowchart LR
A["npx magector init"] --> B["Verify<br/>Project"]
B --> B2["Resolve<br/>Binary"]
B2 --> C["Prepare<br/>Model Dir"]
C --> D["Index<br/>Codebase"]
D --> E["Detect IDE<br/>Cursor · Claude Code"]
E --> E2["API Key<br/>(optional)"]
E2 --> F["Write MCP<br/>Config"]
F --> R["Write IDE Rules<br/>CLAUDE.md · Cursor"]
R --> G["Update<br/>.gitignore"]
2. Search
npx magector search "product price calculation"
npx magector search "checkout totals collector" -l 20
3. Re-index After Changes
npx magector index
4. IDE Setup Only (Skip Indexing)
npx magector setup
Manual MCP configuration
For another MCP client, or to write the config by hand, use the entry init writes (.mcp.json for Claude Code, ~/.cursor/mcp.json for Cursor):
{
"mcpServers": {
"magector": {
"command": "npx",
"args": ["-y", "magector@<version>", "mcp"],
"env": {
"MAGENTO_ROOT": "/path/to/magento",
"MAGECTOR_DB": "/path/to/magento/.magector/index.db"
}
}
}
}
init writes magector@latest; a pinned version is shown here. init adds MAGECTOR_MODELS (absolute) when it is set, and ANTHROPIC_API_KEY when you enter a key.
CLI Reference
Rust Core CLI
magector-core [-v|--verbose] <COMMAND>
Commands:
index Index a Magento codebase
search Search the index semantically
serve Start persistent server mode (stdin/stdout JSON protocol)
describe Generate LLM descriptions for di.xml files (requires an Anthropic API key)
validate Run the validation suite (downloads Magento if no root is given)
download Download Magento 2 Open Source
stats Show index statistics
embed Generate embedding for text
-v, --verbose is a global flag.
index
magector-core index -m <PATH> [OPTIONS]
Options:
-m, --magento-root <PATH> Path to Magento root directory (required)
-d, --database <PATH> Index database path [default: ./.magector/index.db]
-c, --model-cache <PATH> Model cache directory [default: ./models]
--descriptions-db <PATH> Path to descriptions SQLite DB (descriptions are prepended to embeddings)
--threads <N> Max ONNX + rayon threads [default: half of CPU cores for ONNX; also MAGECTOR_THREADS]
--batch-size <N> Embedding batch size [default: 256; also MAGECTOR_BATCH_SIZE]
--force Discard any existing index and rebuild (without it, indexing resumes)
When --descriptions-db is provided (or auto-detected as sqlite.db next to the index), descriptions are prepended to the embedding text as "Description: {text}\n\n" before the raw file content. This places the description within the 256 tokens the embedding model reads.
search
magector-core search <QUERY> [OPTIONS]
Options:
-d, --database <PATH> Index database path [default: ./.magector/index.db]
-c, --model-cache <PATH> Model cache directory [default: ./models]
-l, --limit <N> Number of results [default: 10]
-f, --format <FORMAT> Output format: text, json [default: text]
describe
magector-core describe -m <PATH> [OPTIONS]
Options:
-m, --magento-root <PATH> Path to Magento root directory (required)
-o, --output <PATH> Output SQLite database [default: ./.magector/sqlite.db]
--api-key <KEY> Anthropic API key [default: $ANTHROPIC_API_KEY]
--model <MODEL> Model [default: claude-sonnet-4-5-20250929]
--force Re-describe all files (ignore cache)
Generates natural-language descriptions of di.xml files using the Anthropic API. Descriptions are stored in a SQLite database and used during indexing to enrich embeddings. Only files with changed content hashes are re-described (incremental by default).
serve
magector-core serve [OPTIONS]
Options:
-d, --database <PATH> Index database path [default: ./.magector/index.db]
-c, --model-cache <PATH> Model cache directory [default: ./models]
-m, --magento-root <PATH> Magento root (enables file watcher)
--descriptions-db <PATH> Path to descriptions SQLite DB
--watch-interval <SECS> File watcher poll interval [default: 60]
--threads <N> Max ONNX threads [default: half of CPU cores]
Starts a persistent process that reads JSON requests from stdin and writes JSON responses to stdout. It keeps the ONNX model and the vector index in memory for repeated queries.
When --magento-root is provided, a background file watcher polls for changed files every --watch-interval seconds and re-indexes them without a restart. The MCP server starts serve with --watch-interval set to MAGECTOR_WATCH_INTERVAL (default 300 s). Vectors of modified and deleted files are soft-deleted (tombstoned); new vectors are appended. When more than 20% of the vectors are tombstoned, they are purged (and the HNSW graph, used only above 300k vectors, is rebuilt).
Protocol (one JSON object per line):
On start, serve prints a ready line: {"ok":true,"ready":true,"vectors":N,"watcher":true|false}. Every reply is one JSON object; errors are {"ok":false,"error":"..."}. A request may carry an id field; the reply then carries the same id, copied verbatim. A reply that would not be valid JSON is replaced by {"ok":false,"error":"serve produced invalid JSON: …"}.
// Request:
{"command":"search","query":"product price","limit":10}
// Response:
{"ok":true,"data":[{"id":123,"score":0.85,"metadata":{...}}]}
// Stats request:
{"command":"stats"}
// Watcher status:
{"command":"watcher_status"}
// Response:
{"ok":true,"data":{"running":true,"tracked_files":18234,"last_scan_changes":3,"interval_secs":60}}
// Descriptions (all LLM descriptions from SQLite DB):
{"command":"descriptions"}
// Response:
{"ok":true,"data":{"app/code/Magento/Catalog/etc/di.xml":{"hash":"...","description":"...","model":"claude-sonnet-4-5-20250929","timestamp":1769875137},...}}
// Describe (generate descriptions + re-index the described files):
{"command":"describe","magento_root":"/var/www/html","force":false}
// Response:
{"ok":true,"data":{"total_files":371,"generated":5,"skipped":366,"errors":0}}
// SONA feedback (pattern learning only; no embedding update):
{"command":"feedback","signals":[{"type":"refinement_to_plugin","query":"checkout totals","timestamp":1700000000000}]}
// Response:
{"ok":true,"data":{"learned":1}}
// SONA status:
{"command":"sona_status"}
// Response:
{"ok":true,"data":{"learned_patterns":5,"total_observations":12,"term_patterns":3,"global_observations":12}}
Required fields: query for search, magento_root for describe (optional: output, force, api_key, model), signals for feedback; limit defaults to 10. Feedback signals may also carry searchResultPaths, followedTool, followedArgs, originalQuery and refinedQuery. The values in the examples are illustrative.
The MCP server also uses these internal commands: enrich and enrich_query (method-chain index), ast_query (tree-sitter patterns), grep, process_set / process_get / process_remove and cache_set / cache_get (process state and cache in data.db).
validate, download, stats, embed
magector-core validate [-m <PATH>] [-d <PATH>] [-c <PATH>] [-r <PATH>] [-s]
# -m: Magento root (downloaded if absent); -d [./validation.db]; -c [./models];
# -r, --report [./validation_report.json]; -s, --skip-index (reuse an existing index)
magector-core download [-t <PATH>] [--version <TAG>]
# -t, --target [./magento2]; --version [2.4.7]
magector-core stats [-d <PATH>]
magector-core embed -t <TEXT> [-c <PATH>]
Node.js CLI
npx magector init [path] [--threads n] [--batch-size n] [--force] # Full setup: index + IDE config
npx magector index [path] # Index (or re-index) Magento codebase
npx magector search <query> # Search indexed code
npx magector describe [path] [--force] # Generate LLM descriptions for di.xml files
npx magector stats # Show indexer statistics
npx magector setup [path] # IDE setup only (no indexing)
npx magector snapshot save <file> [path] # Index, then archive the index + PHP scan
npx magector snapshot load <file> [path] # Restore an archive into a root
npx magector snapshot info <file> # Version, root and entries of an archive
npx magector mcp # Start MCP server
npx magector version # Print the version (also --version, -V)
npx magector help # Show help
Options: search -l, --limit <n> (default 10), -f, --format text|json; init and index --threads <n>, --batch-size <n>, --force; describe --force; snapshot save --no-index; snapshot load --update.
Put the path first when you pass both a path and options. init takes the first argument that does not start with - as the path, so init --threads 2 /path would use 2 as the path. describe takes the argument right after describe as the path: use npx magector describe . --force or npx magector describe /path/to/magento --force, not npx magector describe --force.
npx magector validate and npx magector benchmark (and the npm scripts validate, validate:verbose, validate:keep, benchmark) are currently broken: they fail with "Cannot find module …/src/indexer.js". Use magector-core validate instead.
init always writes the index to <path>/.magector/index.db (it ignores MAGECTOR_DB) and does not take the .magector/reindex.pid lock, so do not run it while another indexer is running on the same root.
The describe command and magento_describe MCP tool require an Anthropic API key. During npx magector init, you are prompted to paste your key (optional). If provided, it is stored in the MCP config file as the ANTHROPIC_API_KEY environment variable so the MCP server can use it. For Claude Code that file is <project>/.mcp.json, and init adds only .magector/ to .gitignore: keep .mcp.json out of git, or leave the key out and set ANTHROPIC_API_KEY in your environment or a user-level config instead. You can also set it later by adding "ANTHROPIC_API_KEY": "sk-..." to the env section in .mcp.json or ~/.cursor/mcp.json.
Environment Variables
These defaults apply to the Node.js CLI and the MCP server. The Rust core's own -d flag (see above) defaults to ./.magector/index.db in its working directory, and its -c flag to ./models.
Running in containers
The Linux binaries (x64 and arm64) link libstdc++ statically and need glibc 2.34 or newer: they run in Warden and DDEV PHP containers (CentOS Stream 9 / RHEL 9), Debian 12 and Ubuntu 22.04+. Alpine (musl) is not supported — use a glibc Node image such as node:22-bookworm-slim.
Keep the index and the model beside the code, so they survive container restarts and work offline. Set both variables for every magector command — index, search and the MCP server:
export MAGENTO_ROOT=/var/www/html MAGECTOR_MODELS=/var/www/html/.magector/models
npx -y magector@<version> index # pin the version you deploy
Re-running index embeds only files whose content changed: a checkout or docker cp that only rewrote timestamps costs a content-hash comparison, not a re-embed, and a run that changes nothing leaves index.db untouched. Set MAGECTOR_NO_UPDATE=1 in images and CI so the CLI does not re-run itself as the latest npm version.
Build the index once, restore it where it is used
On slow disks (CI runners, network volumes) indexing and the PHP scan that find_implementors / find_event_dispatchers need take longest. Build both where the code is built — an image build — and restore them in the run:
# image build: index, read the PHP tree, write one archive
npx magector snapshot save /opt/magector.tar.gz /var/www/html
# run: restore it (the old index is kept as index.db.bak)
npx magector snapshot load /opt/magector.tar.gz /var/www/html
The archive (tar, gzip for .gz / .tgz) holds index.db, its manifest, the SONA state (index.sona), the descriptions DB (sqlite.db) and .magector/php-scan.json, plus a sha256 checksum per entry. A restore writes everything beside the live files and replaces them only when every entry is complete and matches its sha256 — computed while the archive is read, so nothing is read twice; a snapshot of another Magector version is refused. It can be restored into another path: the vector index stores paths relative to the root, and the PHP scan is rewritten for the new root. The method-chain enrichment (data.db) is not archived; run magento_enrich after a restore if you use magento_find_null_risks.
Whether anything is read again depends on the files' mtimes. In an image they are the build's, so neither load --update nor the MCP server reads a file again; load says so (it compares mtime and size, without reading). After a fresh checkout with new mtimes, --update hashes those files to find that their content is the same, and the MCP server reads the PHP tree again (a changed file with classes makes the scan unusable). save --no-index archives the existing index as it is. The models are not in the archive: keep MAGECTOR_MODELS in the image.
Constraining CPU usage during indexing
Indexing a large codebase can saturate the CPU during PHASE 2 (ONNX embedding generation). To keep a developer machine responsive while indexing, lower the thread count:
npx magector index --threads 2 # use only 2 cores for both parsing and embedding
MAGECTOR_THREADS=2 npx magector index # equivalent via env var
OMP_NUM_THREADS=2 npx magector index # also honored as a fallback
The --threads flag and MAGECTOR_THREADS / OMP_NUM_THREADS env vars constrain both the rayon thread pool used by PHASE 1 (parallel AST parsing) and the ONNX intra-op thread pool used by PHASE 2 (embedding inference). Without any of them, ONNX uses half of the CPU cores and parsing uses all cores. The active thread source is logged at startup so you can verify it took effect:
INFO Rayon global pool: 2 threads (available: 16)
INFO ONNX intra_threads: 2 (available: 16, source: --threads flag)
For very large or CPU-constrained runs, you may also need to extend the wall-clock timeout (default 4 hours):
MAGECTOR_INDEX_TIMEOUT=28800000 npx magector index --threads 2 # 8 h timeout, 2 threads
Resume after timeout or interrupt
Indexing saves its progress to disk every 50 embedding batches. If the process is killed or times out mid-run, re-run npx magector index — it resumes from the last save:
npx magector index
# ♻️ Resuming from previous run: <N> vectors across <M> files already indexed
# ✓ Found <T> total files; <U> unchanged, <P> to process
The indexer compares the tree with the existing index (content hash per file) and embeds only files that are new or whose content changed since the last save; the vectors of unchanged files are kept. A resumed run also picks up files added to the tree since the previous run.
To force a full rebuild (e.g. to discard stale vectors), pass --force:
npx magector index --force
One indexer per root
Only one indexer writes a root's index at a time. magector index and magento_index take the lock file .magector/reindex.pid (created exclusively); a second run is refused while the PID in it is alive, including a process of another user, and magento_describe does not save the index while the lock is held. If a run reports another indexer and none is running (for example after a crash or a container restart), delete .magector/reindex.pid.
Directories that cannot be read are skipped instead of failing the run; on an incremental run, files already indexed under them are kept. A missing or unreadable root still fails.
MCP Server Tools
The MCP server exposes 48 tools for AI-assisted Magento 2 and Adobe Commerce development. Parameters marked * are required.
Output Format
Semantic result lists (magento_search, and the semantic block of the finder tools) are structured JSON. Structural tools (DI, events, diff, complexity, stats, grep, read and others) answer in Markdown/text.
{
"results": [
{
"rank": 1,
"score": 0.892,
"path": "vendor/magento/module-catalog/Model/ProductRepository.php",
"module": "Magento_Catalog",
"className": "ProductRepository",
"namespace": "Magento\\Catalog\\Model",
"methods": ["save", "getById", "getList", "delete", "deleteById"],
"magentoType": "repository",
"fileType": "php",
"badges": ["repository"],
"snippet": "class ProductRepository implements ProductRepositoryInterface..."
}
],
"count": 1
}
Key fields:
methods-- list of method names in the class (avoids needing to read the file)badges-- role indicators:plugin,controller,observer,repository,graphql-resolver,model,blocksnippet-- first 300 characters of indexed content (top 3 results only)- Optional fields when known:
methodName,area,description(LLM description) andcodePreview(source lines for the top 3 PHP/PHTML results, or the full method body frommagento_find_method)
How exact are the results?
Every answer is one of four kinds. The tables below say which kind each tool returns, and where it can return less than the codebase contains — the case to watch when you search for everything a change affects.
DI and events (pass a fully qualified name)
With a short name (no namespace) the DI tools fall back to fuzzy matching — useful for exploring, not for a complete impact list.
Every tool answer is capped at 40,000 characters (MAGECTOR_MAX_OUTPUT_CHARS); a longer answer ends with
Output truncated — narrow the query (full class name, targetMethod, a namespace) rather than read on.
Ambiguity is reported as a problem of the project, not of the tool: when two modules declare the same
preference, plugin or observer, neither depends on the other (no <sequence>, no composer require)
and swapping them changes the result, the outcome depends on incidental module order and can change
with an update.
Other structural answers
Configuration Magento rejects
The DI and event tools read what the files say. When Magento cannot load a file, or reads a value
differently than written, they say so first: the answers of find_plugin, find_observer,
find_preference, find_di_wiring, find_event_flow, trace_dependency, impact_analysis,
find_class and batch start with a notice listing the rejected files of enabled modules, and
values in the answer's files that do not take effect as written. magento_validate_config gives the
details, with Magento's own messages:
The built-in check does not validate schemas (developer mode) and cannot check const /
init_parameter arguments (PHP's defined()). The native check was verified on PHP 8.1, 8.3 and 8.4 (on Magento 2.4.5
with PHP 8.4 it gives the same result as with PHP 8.1).
The results are static analysis of the files. For a running installation, the object manager
configuration read at runtime remains the reference — note that bin/magento dev:di:info lists plugins
disabled with disabled="true" as active.
Search Tools
Magento-Specific Finders
Flow & Dependency Tracing
magento_trace_flow auto-detects the entry type from the pattern (/V1/... → API, snake_case → event, camelCase → GraphQL, path/segments → route), or override it with entryType. Use depth: "shallow" (entry + config + plugins) or depth: "deep" (adds observers, layout, templates, DI preferences).
Impact & Testing
Diagnostics
Analysis Tools
Utility Tools
Null-Safety Analysis
Search Behaviour
- Hybrid BM25+vector search -- combines text frequency scoring with semantic vector similarity
- Query expansion --
magento_searchexpands queries with Magento domain synonyms (plugin → interceptor, checkout → cart/quote/totals, etc.); turn it off withexpand: false - Module filtering --
moduleFilter/excludeModuleFilteronmagento_searchlimit results by vendor/module pattern. Accepts a single string or array of strings. Supports wildcards, e.g.,"Vendor_*"or["Acme_PaymentGateway", "Acme_FreeShipping"] - Non-blocking automatic reindex -- when the MCP server starts an index itself (no index, or an incompatible one), the old index stays usable; the new one is built to a temp path and swapped in on completion (the old one is kept as
.bak).magento_indexandmagector indexwrite the index in place
Tool Cross-References
Each tool description includes "See also" hints to help AI clients chain tools:
graph LR
cls["find_class"] --> plg["find_plugin"]
cls --> prf["find_preference"]
cls --> mtd["find_method"]
cfg["find_config"] --> obs["find_observer"]
cfg --> prf
cfg --> api["find_api"]
plg --> cls
plg --> mtd
tpl["find_template"] --> blk["find_block"]
blk --> tpl
blk --> cfg
dbs["find_db_schema"] --> cls
gql["find_graphql"] --> cls
gql --> mtd
ctl["find_controller"] --> cfg
trc["trace_flow"] -.-> ctl
trc -.-> plg
trc -.-> obs
trc -.-> tpl
trc -.-> api
trc -.-> gql
dep["trace_dependency"] --> prf
dep --> plg
evf["find_event_flow"] --> obs
imp["impact_analysis"] --> dep
imp --> cls
tst["find_test"] --> cls
err["error_parser"] --> dep
lay["find_layout"] --> blk
style cls fill:#4a90d9,color:#fff
style mtd fill:#4a90d9,color:#fff
style cfg fill:#e8a838,color:#000
style plg fill:#d94a4a,color:#fff
style obs fill:#d94a4a,color:#fff
style prf fill:#e8a838,color:#000
style api fill:#e8a838,color:#000
style tpl fill:#68b684,color:#000
style blk fill:#68b684,color:#000
style dbs fill:#9b59b6,color:#fff
style gql fill:#9b59b6,color:#fff
style ctl fill:#4a90d9,color:#fff
style trc fill:#2ecc71,color:#000
Query Examples
magento_search("how are checkout totals calculated")
magento_search("product price with tier pricing and catalog rules")
magento_find_class("ProductRepositoryInterface")
magento_find_method("getById")
magento_find_config("di.xml plugin for ProductRepository")
magento_find_plugin({ targetClass: "Topmenu" })
magento_find_observer("sales_order_place_after")
magento_find_preference("StoreManagerInterface")
magento_find_api("/V1/orders")
magento_find_controller("catalog/product/view")
magento_find_graphql("placeOrder")
magento_find_db_schema("sales_order")
magento_find_cron("indexer")
magento_find_block("cart totals")
magento_find_template("minicart")
magento_analyze_diff({ commitHash: "abc123" })
magento_complexity({ module: "Magento_Catalog", threshold: 10 })
magento_describe()
magento_trace_flow({ entryPoint: "checkout/cart/add", depth: "deep" })
magento_trace_flow({ entryPoint: "/V1/products" })
magento_trace_flow({ entryPoint: "placeOrder", entryType: "graphql" })
magento_trace_flow({ entryPoint: "sales_order_place_after" })
magento_trace_data_flow({ attributeKey: "custom_discounted_price_incl_tax", modelClass: "Quote\\Address" })
magento_find_event_dispatchers({ eventName: "custom_discount_rule_validation_before" })
magento_find_implementors({ interfaceName: "ProductRepositoryInterface" })
magento_find_callers({ methodName: "collectTotals", className: "TotalsCollector" })
magento_find_di_wiring({ className: "CartManagementInterface" })
magento_trace_call_chain({ className: "Magento\\Quote\\Model\\QuoteManagement", methodName: "submit" })
Supported Platforms
Pre-built binaries are provided for the following platforms:
Note: macOS Intel (x86_64) is not supported as a pre-built binary. Intel Mac users can build from source.
Validation
Magector is checked at two levels:
- Automated tests --
npm testruns the Node.js test files (unit tests, MCP integration over stdio JSON-RPC, reindex lock, serve start and request ids, DI parsing and resolution, event dispatch resolution, config models,validate_config,trace_graphql, index snapshot).cd rust-core && cargo test --releaseruns the Rust unit tests. CI runs both (see Testing). - Rust-level validation --
magector-core validateruns 557 validation queries (defined inrust-core/src/validation.rs) against Magento 2.4.7 (downloaded when no-mis given) or the root given with-m.
Historical accuracy result
tests/results/accuracy-report.json records an E2E MCP accuracy run from 2 April 2026: 99.2 / 100 (grade A+) on 101 queries across 16 tools, 101/101 passed, precision 98.4%, MRR 99.5%, NDCG@10 98.8%. That run used an earlier build with the all-MiniLM-L6-v2 embedding model, before the switch to bge-small-en-v1.5. The test suite that produced it has since been removed from the repository, so the result cannot be re-run from the current repo and is not a statement about the current build.
Project Structure
magector/
├── src/ # Node.js: CLI (cli.js), MCP server (mcp-server.js), binary and model
│ │ # resolution, init/IDE setup, DI/config readers, PHP scan, snapshots
│ ├── php/ # PHP program for the native magento_validate_config
│ ├── templates/ # IDE rules templates (CLAUDE.md section, Cursor rules)
│ └── validation/ # Legacy JS validation/benchmark scripts (broken: import removed modules)
├── tests/ # Node.js tests run by `npm test` (scripts/run-tests.mjs), fixtures, results/
├── rust-core/ # Rust core (magector-core)
│ ├── src/ # main.rs (CLI + serve), indexer, embedder, vectordb, watcher, ast,
│ │ # magento (pattern detection), describe, sona, datadb, validation
│ ├── queries/ # tree-sitter queries
│ └── models/ # dev fallback model directory (tokenizer.json committed)
├── platforms/ # npm packages for the platform binaries (built by CI, not committed)
├── scripts/ # run-tests.mjs, sync-optional-deps.mjs, setup.sh, verify-magento/
├── config/ # example MCP config for a source checkout (its paths must be edited)
├── docs/ # verification notes
├── .github/workflows/ # ci.yml (tests), release.yml (cross-compile + publish)
├── CHANGELOG.md
├── package.json
└── LICENSE
How It Works
1. Indexing
Magector scans every .php, .js, .xml, .phtml, and .graphqls file in a Magento 2 or Adobe Commerce codebase (files up to 100 KB), plus each module README at app/code/<Vendor>/<Module>/README.md:
- AST parsing -- Tree-sitter extracts class names, namespaces, methods, inheritance, and interface implementations from PHP and JavaScript files
- Pattern detection -- Identifies Magento file types by path and AST: controllers, models, repositories, plugins, observers, blocks, GraphQL resolvers, cron, setup, API interfaces, configuration XML types, and more
- Search text enrichment -- Combines AST metadata with Magento pattern keywords to create the text that is embedded
- Description enrichment -- If a descriptions SQLite DB is present, LLM-generated descriptions are prepended to the embedding text as
"Description: {text}\n\n", placing DI concepts (preferences, plugins, virtual types, subsystem names) within the 256 tokens the model reads - Embedding -- ONNX Runtime generates 384-dimensional vectors with bge-small-en-v1.5 (mean-pooled, L2-normalised, first 256 tokens)
- Indexing -- Vectors are stored in
index.db; up to 300k of them a search compares the query with every vector (exact, nothing to build at start-up), above that an HNSW graph finds approximate nearest neighbors
Each source file produces one vector. A module README is split into sections of at most 800 characters, one vector each, led by the module name and the section heading; magento_find_class and magento_find_method never return README vectors. After upgrading, run magector index --force to re-split READMEs indexed by an older version (otherwise a README is re-split when it changes).
2. Searching
magento_searchexpands the query with Magento synonyms (e.g. "controller" adds "action class", "execute method", "route handler"; disable withexpand: false)- The Rust core prefixes the query with
Represent this sentence:and embeds it into the same 384-dimensional space - The nearest vectors are found by cosine similarity (every vector compared up to 300k, the HNSW graph above)
- Hybrid reranking boosts results with keyword matches in path and search text
- SONA adjustment -- learned pattern boosts raise result types that followed similar searches before
- Results are returned as structured JSON with file path, class name, methods, role badges, and (top results) a content snippet
3. Persistent Serve Mode
The MCP server spawns a persistent Rust process (magector-core serve) that keeps the ONNX model and the vector index loaded in memory. Queries are sent as JSON over stdin and responses returned via stdout, so a query does not load the model or the index. Up to 300k vectors there is no search graph to build at start; on a 50k-vector index serve was ready 0.7 s after it started (CHANGELOG 2.22.0). If it is not ready, a search waits for it up to MAGECTOR_SERVE_WAIT_MS, then runs a single-shot magector-core search (asynchronous, at most 20 s), so a tool answers inside the MCP client's 60 s. One serve process per project: the first MCP instance starts it and the others join it over .magector/serve.sock — also instances in other containers sharing .magector (an MCP gateway starting one per session), since the lock records the PID namespace of its holder.
flowchart LR
subgraph startup ["Startup (once)"]
S1["Load Model"] --> S2["Load Index"] --> S3["Ready Signal"]
end
startup --> query
subgraph query ["Per Query"]
Q1["stdin JSON"] --> Q2["Embed"] --> Q3["Vector Search"] --> Q4["Rerank"] --> Q5["stdout JSON"]
end
subgraph fallback ["Fallback (serve not ready)"]
F1["single-shot search<br/>(async, ≤20 s)"]
end
style startup fill:#e8f4e8,color:#000
style query fill:#e8e8f4,color:#000
style fallback fill:#f4e8e8,color:#000
4. File Watcher (Incremental Re-indexing)
When the serve process is started with --magento-root, a background thread polls the filesystem for changes: every 300 s when started by the MCP server (MAGECTOR_WATCH_INTERVAL), every 60 s by default when magector-core serve is run directly (--watch-interval). Changed files are re-indexed without restarting the server.
Since hnsw_rs does not support point deletion, Magector uses a tombstone strategy: old vectors for modified/deleted files are marked as tombstoned and filtered out of search results. New vectors are appended. When more than 20% of the vectors are tombstoned, they are purged; the HNSW graph, used only above 300k vectors, is rebuilt.
flowchart LR
W1["Sleep (interval)"] --> W2["Scan Filesystem"] --> W3{"Changes?"}
W3 -->|No| W1
W3 -->|Yes| W4["Tombstone Old Vectors"] --> W5["Parse + Embed New Files"] --> W6["Append Vectors"] --> W7{"Tombstone > 20%?"}
W7 -->|Yes| W8["Compact<br/>(purge tombstones)"] --> W9["Save to Disk"]
W7 -->|No| W9
W9 --> W1
style W4 fill:#f4e8e8,color:#000
style W5 fill:#e8f4e8,color:#000
style W8 fill:#e8e8f4,color:#000
5. MCP Integration
The MCP server delegates search and index operations to the Rust core binary. Analysis tools (diff, complexity) use ruvector JS modules directly.
sequenceDiagram
participant Dev
participant AI
participant MCP
participant Rust
participant VectorDB
Dev->>AI: "checkout totals?"
AI->>MCP: magento_search(...)
MCP->>Rust: JSON query
Rust->>VectorDB: embed + search
VectorDB-->>Rust: candidates
Rust-->>MCP: JSON results
MCP-->>AI: paths, methods, badges
AI-->>Dev: TotalsCollector.php
6. SONA Feedback Learning
The MCP server tracks sequences of tool calls and sends feedback signals to the Rust process, which adjusts search result rankings based on observed usage patterns.
How it works: The Node.js SessionTracker watches for follow-up tool calls after magento_search. If a user searches and then calls magento_find_plugin, SONA learns that similar queries should boost plugin results. The learned weights are persisted to a .sona file next to the index.
Characteristics:
- Score adjustments are capped at ±0.15 so they do not override semantic similarity
- Learning rate decays with repeated observations (diminishing returns)
- Learned weights are keyed by normalized, order-independent query term hashes
- 3-tier scoring with negative learning: positive signals boost the followed feature type, mild negative learning (0.1×) demotes unrelated types
- Always active -- no feature flags or build-time opt-in required
- Persisted via bincode next to the index, with its extension replaced (
.magector/index.db→.magector/index.sona); the V1 → V2 persistence format is upgraded on load
MicroLoRA + EWC++ (present, not trained online)
rust-core/src/sona.rs also contains embedding-level adaptation:
adjust_query_embedding() applies the LoRA transform + L2 normalization at search time, with a cosine similarity guard (≥0.90) that skips destructive adjustments. learn_with_embeddings(), the only function that trains the adapter, is called only from tests: the serve feedback command does pattern learning only. The adapter is therefore not trained in normal use.
A January 2026 evaluation (tests/results/sona-eval-report.json, 180 evaluation queries after 3 training rounds) found no measurable ranking change: MRR −0.002, 178 of 180 queries kept their rank. The script that produced it has since been removed from the repository.
7. LLM Description Enrichment
Magector can generate natural-language descriptions of di.xml files using the Anthropic API and embed them into the vector index, so queries about dependency injection can match di.xml files by their described purpose.
Workflow:
# 1. Generate descriptions (incremental — only re-describes changed files)
ANTHROPIC_API_KEY=sk-... npx magector describe /path/to/magento
# 2. Re-index with descriptions embedded into vectors
npx magector index /path/to/magento
Or via the MCP tool: magento_describe() generates descriptions and re-indexes the described files in one step.
How it works: Each di.xml file is sent to Claude Sonnet (claude-sonnet-4-5-20250929 by default) with a prompt aimed at search retrieval. The resulting description is stored in a SQLite database (.magector/sqlite.db). During indexing, descriptions are prepended to the embedding text as "Description: {text}\n\n" before the raw file content, placing the described terms (preferences, plugins, virtual types, subsystem names) within the model's 256-token window.
Magento Patterns Detected
mindmap
root((Patterns))
PHP
Controller
Model
Repository
Block
Helper
API interface
Setup / Patch
Console
Cron
Interception
Plugin
Observer
XML
di.xml
events.xml
webapi.xml
system.xml
acl.xml
crontab.xml
db_schema.xml
layout
Frontend
Template
JavaScript
GraphQL
Docs
Module README
Each file gets a Magento type from its path (rust-core/src/magento.rs); PHP files also get pattern flags from their AST (rust-core/src/ast.rs):
Configuration
IDE Rules
npx magector init (or setup) writes .cursor/rules/magector.mdc for Cursor and a Magector section in CLAUDE.md for Claude Code — for the detected IDE, or both when neither is detected. The rules instruct the AI to:
- Use Magector MCP tools before reading files manually
- Write effective semantic queries
- Follow Magento development patterns
- Interpret search results correctly
Excluding Directories (.magectorignore)
Magector skips these during indexing:
vendor/bin/,pub/static/,dev/tests/,dev/tools/(by path from the root)- any directory named
node_modules,.git,var,generated,Test,Tests,test,tests,_files,fixturesorperformance-toolkit - files larger than 100 KB, and directories that cannot be read
vendor/ itself is indexed: on a composer install, Magento core lives in vendor/magento.
For project-specific exclusions, create a .magectorignore file in your Magento project root:
# .magectorignore — additional directories to exclude from Magector indexing
# One directory name or path prefix per line (no wildcards, no negation)
# Custom exclusions
pub/media
setup
update
phpserver
bin
lib/internal
Pattern rules:
- Lines starting with
#are comments - Empty lines are ignored
- Trailing slashes are stripped (
vendor/→vendor) - Patterns without
/match directory names anywhere in the tree - Patterns with
/match relative paths from the project root (the path itself and everything under it) - Patterns match directories only
Config Data (core_config_data exports)
The magento_trace_config tool can show actual database config values alongside code analysis. Export your core_config_data table as JSON and place files in .magector/config-data/.
Each *.json file in that directory must hold a JSON array of objects with the fields scope, scope_id, path and value:
[
{"scope": "default", "scope_id": 0, "path": "web/secure/base_url", "value": "https://shop.example/"},
{"scope": "websites", "scope_id": 1, "path": "payment/checkmo/active", "value": null}
]
With the mysql client, export the table as tab-separated text (-B) and convert it with Python:
mysql -u user -p magento_db -B -e "SELECT scope, scope_id, path, value FROM core_config_data" \
| python3 -c 'import csv,json,sys; r=list(csv.DictReader(sys.stdin, delimiter="\t", quoting=csv.QUOTE_NONE)); [d.update(scope_id=int(d["scope_id"]), value=None if d["value"]=="NULL" else d["value"]) for d in r]; json.dump(r, sys.stdout, indent=2)' \
> .magector/config-data/CZ-production.json
In -B output SQL NULL appears as the text NULL (converted to null above), and tabs, newlines and backslashes inside values are escaped (\t, \n, \\). Any other tool that writes the same array works too.
File naming: the file name without .json is the environment label shown in the answer; the convention is {country}-{environment}.json, e.g.:
CZ-production.jsonSK-staging.jsonIT-production.json
When magento_trace_config traces a config path, it looks up values from all exports and shows them per environment. A file that is not valid JSON is skipped; a JSON value that is not an array counts as empty. The exports are read once per MCP server process: restart the server after adding or changing a file.
Model Configuration
The Rust core indexes and searches with bge-small-en-v1.5 (384 dimensions). It loads bge-small-en-v1.5.onnx and tokenizer.json from its model directory and downloads both from Hugging Face (BAAI/bge-small-en-v1.5) when either is missing; an interrupted download is retried on the next start. The Node.js CLI and MCP server pick that directory in this order: MAGECTOR_MODELS if it already holds the model files the Node side checks for (see below), then ~/.magector/models, then rust-core/models in a source checkout. When none is ready, the CLI downloads into MAGECTOR_MODELS (or ~/.magector/models); the MCP server falls back to MAGECTOR_MODELS, else ./models. A direct magector-core call uses -c (default ./models in the working directory):
magector-core index -m /path/to/magento -c /custom/model/path
The Node.js side (src/model.js) checks the model directory for all-MiniLM-L6-v2.onnx and tokenizer.json and downloads them from Hugging Face (sentence-transformers/all-MiniLM-L6-v2) when they are missing. It uses these files only to decide whether the directory is ready; the Rust core never loads the MiniLM model, and on its first run it replaces tokenizer.json with the bge tokenizer. As a result, the model directory also holds an unused all-MiniLM-L6-v2.onnx (about 8