sf-context-engine
v1.3.0
Published
Index Salesforce metadata and source evidence into SQLite. Six MCP tools provide dependency analysis, matched snippets, and Apex predicate inspection.
Maintainers
Readme
sf-context-engine indexes your entire Salesforce DX project into a SQLite dependency graph and serves it through an MCP server. Works with any MCP-compatible client — Claude Code, Cursor, Windsurf, Claude Desktop, Zed, and more. One tool call replaces dozens of file searches.
- 12 typed parsers — objects, fields, flows, Apex, LWC, layouts, profiles, permissions, reports, and more
- 18,600+ dependency relationships across 41 ref types — lookups, triggers, embeds, grants, formula refs, query predicates
- Sub-millisecond queries on a portable SQLite file with FTS5 full-text search
- ~1.4s full index, ~120ms incremental — content-hash based, only re-parses what changed
Why?
Without sf-context-engine, every Salesforce question triggers a cascade:
You: "What LWCs are on the Opportunity record page?"
LLM: glob flexipages/*Opportunity* → 4 files
read 2,000+ lines of XML each → 8K tokens burned
grep for componentName → partial list
realize Quick Actions launch LWCs too → more searching
grep quickActions/*Opportunity* → 3 more files
read each for lightningWebComponent → finally finds the LWC
...still hasn't checked permissions6+ tool calls. 10K+ tokens. 30+ seconds. Incomplete answer.
With sf-context-engine:
You: "What LWCs are on the Opportunity record page?"
LLM: describe_object("Opportunity") → complete answer, one callFields, LWC components, flows, Apex, layouts, permissions, reports, relationships — all from a single graph traversal in under 5ms.
Quick Start
# Install in your Salesforce DX project
npm install sf-context-engine
npm run build # if installing from source
# Initialize for Claude Code (default)
npx sf-context-engine init
# Or wire a specific MCP client
npx sf-context-engine init --client cursor
npx sf-context-engine init --client codex
npx sf-context-engine init --client auto
# Build the index
npx sf-context-engine index
# Enrich with org-level dependencies (Tooling API)
npx sf-context-engine enrich
# Verify
npx sf-context-engine statusThat's it. Start your configured client — the MCP tools are available as soon as the index exists.
Upgrading from v1.2 or earlier: run
npx sf-context-engine indexonce after updating. The database schema migrates automatically, and the first index run backfills source text and dependency evidence. Restart the MCP client afterward.
What init does
- Verifies
sfdx-project.jsonexists (you're in a Salesforce DX project) - Creates
.sf-context/with a SQLite database - Prompts for an org alias (or pass
--org-alias myOrg) - Wires one or more client configs depending on
--client - Wires SessionStart + SubagentStart hooks into
.claude/settings.local.jsonwhenclaude-codeis selected - Copies bundled Claude Code skills when
claude-codeis selected - Adds
.sf-context/to.gitignore
Supported init targets
| --client value | What gets configured |
|------------------|----------------------|
| claude-code (default) | Project .mcp.json + .claude/settings.local.json hooks |
| cursor | Global Cursor MCP config at ~/.cursor/mcp.json |
| codex | Global Codex MCP config at ~/.codex/config.toml |
| kilo | Project Kilo MCP config at .kilocode/mcp.json |
| claude-desktop | Claude Desktop config at ~/Library/Application Support/Claude/claude_desktop_config.json |
| auto | Claude Code project files plus any detected installed clients above |
Using with other MCP clients
The MCP server is standard stdio and works with any client that supports MCP. If your client doesn't use the built-in init --client ... wiring, point it at the package CLI's serve subcommand:
Cursor / Windsurf / Zed:
Add to your MCP config (the exact location varies by client):
{
"mcpServers": {
"sf-context-engine": {
"command": "node",
"args": ["node_modules/sf-context-engine/dist/cli/index.js", "serve"],
"env": {
"SF_CONTEXT_PROJECT_ROOT": "/path/to/your/salesforce-project"
},
"cwd": "/path/to/your/salesforce-project"
}
}
}Claude Desktop:
Add to claude_desktop_config.json:
{
"mcpServers": {
"sf-context-engine": {
"command": "node",
"args": ["node_modules/sf-context-engine/dist/cli/index.js", "serve"],
"env": {
"SF_CONTEXT_PROJECT_ROOT": "/path/to/your/salesforce-project"
},
"cwd": "/path/to/your/salesforce-project"
}
}
}Any MCP client (generic):
Run node node_modules/sf-context-engine/dist/cli/index.js serve. The server uses stdio transport and expects either:
cwdto be your Salesforce DX project root- or
SF_CONTEXT_PROJECT_ROOT=/path/to/project
Note:
claude-codeis the only client target with context injection today. Other clients get the same 6 MCP tools, but not the SessionStart/SubagentStart hot-tier hooks.Note: Cursor, Codex, and Claude Desktop use client-level config files, so
initpins those entries to the current project root. Re-runinitfrom another project to retarget them.Note:
initprefers the project's localnode_modules/sf-context-engineinstall when wiring MCP clients. If you runinitfrom an ephemeralnpxcache, it falls back tonpx --yes sf-context-engine serveso the generated config still works.
MCP Tools
Six tools, all available through any configured MCP client after init:
describe_object
The flagship tool. Returns the complete profile of any Salesforce object by walking the full dependency graph:
| Section | What you get | |---------|-------------| | Fields | Every field with type, label, required flag, lookup target | | Validation Rules | Full formulas, error messages, active/inactive status | | Record Types | Names, labels, active status | | Lightning Pages | FlexiPages with embedded LWC/field/action counts | | LWC Components | Direct page embeds + indirect via Quick Actions, with Apex call chains | | Quick Actions | What each action launches — LWC, Flow, or record form | | Flows | Record-triggered (separated from other references), with trigger type | | Apex | Classes with auto-classified roles (Service, Batch, Controller, etc.) | | Page Layouts | Field counts, related lists, surfaced actions | | Security | Profile/permission set CRUD grants in a scannable table | | Reports | Every report referencing this object's fields | | Relationships | Lookup/master-detail connections in both directions | | List Views | All list views with labels |
Use the sections parameter to request only what you need and save tokens:
describe_object("Opportunity", sections: ["flows", "apex", "lwc"])
→ ~640 tokens instead of ~3,700find_dependencies
Impact analysis for any metadata component. Supports multi-hop traversal, cursor pagination, and call-site evidence:
find_dependencies("Account.Industry__c", direction: "downstream", depth: 2)
→ Hop 1: VRs referencing this field, layouts displaying it, FLS grants
→ Hop 2: profiles granting those permissions, reports using those layoutsFor indexed Apex and LWC paths, edges include caller/callee method names, passed arguments, line numbers, and source snippets. Apex field edges distinguish selects_field, filters_on, groups_by, and orders_by instead of treating every field occurrence as a generic reference.
Use page_size and the returned next_cursor for large dependency graphs. Pagination is returned in both the text response and MCP structuredContent.
inspect_apex
Method- and statement-level Apex inspection with exact source evidence:
inspect_apex("CaseKnowledgeService", method: "findArticles")
→ method signature and line range
→ static SOQL projection, WHERE predicate, filter fields, and security mode
→ calls with target methods and passed arguments
→ DML, constants, and dynamic-query proof limitationsWhen a dynamic query predicate cannot be proven from static source, the tool reports that limitation explicitly instead of inferring runtime filtering.
search_metadata
Full-text search across all 3,200+ indexed items:
search_metadata("Sharepoint", metadata_type: "ApexClass")
search_metadata("Budget", parent_name: "Opportunity", sub_type: "Currency")Prefix matching by default — Account finds AccountService, AccountTrigger, etc. Results include matched source snippets with file and line locations, plus cursor pagination and MCP structuredContent.
get_object_list
Quick overview of the org's data model:
get_object_list(filter: "Budget")
→ Table: Object | Label | Fields | VRs | Record Typesget_index_status
Index health, freshness, and tool usage statistics for the current session.
What Gets Indexed
12 Typed Parsers + Generic Fallback
Every parser extracts both structure (metadata) and connections (dependencies):
| Parser | Source | Key Extractions |
|--------|--------|----------------|
| Object | .object-meta.xml | Sharing model, features |
| Field | fields/*.field-meta.xml | Type, required, formula, lookup target |
| Validation Rule | validationRules/*.validationRule-meta.xml | Formula, error message, field refs |
| Flow | .flow-meta.xml | Trigger object/type, CRUD ops, Apex calls, subflows, email alerts |
| Apex Class | *.cls source code | Methods, SOQL projections/predicates/security modes, field usage, call sites/arguments, DML targets, constants, annotations; auto-classifies role |
| LWC | *.js + .js-meta.xml + HTML | Apex imports, schema refs, child components, object targets |
| Quick Action | .quickAction-meta.xml | LWC/Flow launches, target objects, field refs |
| FlexiPage | .flexipage-meta.xml | Embedded LWCs, Dynamic Forms fields, Quick Actions |
| Layout | .layout-meta.xml | Fields, related lists, actions |
| Permission Set | .permissionset-meta.xml | FLS, CRUD, Apex class access, custom permissions |
| Profile | .profile-meta.xml | FLS, CRUD, layout assignments, app/RT visibility |
| Report/ReportType | .report-meta.xml | Report type, field refs from columns/filters/groupings |
| Generic | Any -meta.xml | Name, type, label for 40+ additional types |
41 Dependency Types
Relationships lookup, master_detail
Flow automation triggers_on, lookups, creates, updates, invokes, calls_subflow,
sends_alert, references
Apex code queries, calls, instantiates, extends, implements, dml_on,
selects_field, filters_on, groups_by, orders_by
LWC calls (apex), references (schema), composes (child), targets (object)
UI surface page_for, embeds, launches, surfaces_action, displays_field,
layout_for, action_on, shows_related_list
Security grants_field_access, grants_object_access, grants_class_access,
grants_permission, grants_tab_access, grants_app_access,
grants_recordtype_access, assigns_layout
Validation validates, formula_ref
Reporting reports_on, uses_report_type, based_on, joinsCLI
| Command | Description |
|---------|-------------|
| sf-context-engine init [--client <client>] | Wire up Claude Code by default, or target Cursor, Codex, Kilo, Claude Desktop, or auto. |
| sf-context-engine index | Parse force-app/ into SQLite. Incremental by default. |
| sf-context-engine index --full | Force full reindex. |
| sf-context-engine status | Index health, item/dep counts, tool usage stats. |
| sf-context-engine audit | Coverage analysis, orphan deps, file comparison. |
| sf-context-engine enrich | Query Tooling API (MetadataComponentDependency) for field-level org dependencies. Requires authenticated org. |
| sf-context-engine serve | Start the MCP server over stdio for generic MCP hosts. |
Observability
Every MCP tool call is logged to .sf-context/logs/YYYY-MM-DD.jsonl:
{"ts":"2026-03-29T07:25:08.250Z","level":"info","event":"tool_call","tool":"describe_object","params":{"objectName":"Account"},"duration_ms":2,"response_chars":14665,"estimated_tokens":3667}The status command shows aggregated tool usage:
Tool usage (today):
describe_object 3 calls ~ 8,200 tokens 2ms avg
search_metadata 5 calls ~ 1,400 tokens 0ms avg
find_dependencies 2 calls ~ 3,100 tokens 1ms avg
Total 10 calls ~ 12,700 tokens 1ms avgThe audit command compares the index against files on disk:
--- File Coverage ---
Files on disk: 3,266
Files in index: 3,266
Missing from index: 0
Stale in index: 0Architecture
force-app/ ──▶ 12 TYPED PARSERS ──▶ SQLite (.sf-context/index.db)
+ generic fallback ├── metadata_items (3,200+ rows)
├── dependencies (18,600+ rows)
~1.4s full ├── metadata_fts (FTS5)
~120ms incremental └── index_runs
Tooling API ──▶ enrich command ──▶ Org-level deps merged into SQLite
MetadataComponentDep ├── Field-level deps for Apex & LWC
~12s per org └── context: org:field / org:*_object
SessionStart Hook ◀──── reads index, injects ~1K tokens
SubagentStart Hook ◀──── reads index, injects ~136 tokens
MCP Server (stdio) ◀──── reads index, serves 6 tools- SQLite with WAL mode — sub-millisecond queries, zero setup, single portable file
- Incremental reindex via SHA-256 content hashing — only re-parses changed files
- Crash-resilient MCP server — uncaught exception handlers, graceful degradation without an index
- Separate hook process — SessionStart/SubagentStart work even if MCP fails to start
Roadmap
v1.0 — Offline Index + MCP (current)
- [x] 12 typed parsers + generic fallback (43 metadata types)
- [x] 18,600+ dependency relationships across 41 ref types
- [x] MCP server with 6 tools, source evidence, graph-walking, multi-hop traversal
- [x] FTS5 full-text search with prefix matching and filters
- [x] Incremental reindex via content hashing
- [x] Claude Code integration (SessionStart + SubagentStart hooks,
.mcp.jsonwiring) - [x] CLI: init, index, status, audit, serve
- [x] Observability: structured logging, tool call timing, token estimation
- [x] 101 tests across 14 test files
v1.x — Multi-Client Support
- [x]
init --client cursor— wire into Cursor MCP config - [x]
init --client codex— wire into Codex MCP config - [x]
init --client kilo— wire into Kilo MCP config - [x]
init --client claude-desktop— wire intoclaude_desktop_config.json - [x]
init --client auto— detect installed clients and configure all of them - [ ] Client-specific context injection (equivalent to Claude Code hooks for other clients)
- [x] Standalone MCP server mode (
sf-context-engine serve) for generic MCP clients
v2.0 — Org Enrichment (in progress)
- [ ]
sf sobject describefor complete schema data (fields, picklists, record types not in source) - [x] MetadataComponentDependency (Tooling API) for org-level dependency graph —
enrichcommand - [x] Merge org data into local index — field-level deps for Apex & LWC, marked with
org:*context - [ ] Drift detection: index vs. live org comparison
Future
- [ ] Large org scaling / domain clustering
- [ ] Plugin system for custom parsers
Requirements
- Node.js >= 18
- Salesforce DX project with retrieved metadata in
force-app/ - Any MCP-compatible client — Claude Code, Cursor, Windsurf, Claude Desktop, Zed, or any tool supporting the Model Context Protocol
Contributing
Contributions welcome. Please open an issue or email [email protected] to discuss what you'd like to change.
License
MIT - Frederik Pardon
