lachesis-mcp
Exposes Lachesis's code navigation tools over MCP, letting LLM agents ask precise compiler-level questions about dataflow, taint, callers, and guards in source code.
README
Lachesis
A compiler-precise code property graph (CPG) with an embedded columnar graph store and a navigation layer built for security reasoning over source code.
Lachesis parses a codebase into a layered graph. It captures syntax, symbols, calls, and a full dataflow tier (value-flow, points-to, taint, and aliasing). It then writes that graph to an embedded Kùzu database and hands it to tools and LLM agents through a navigation API and an MCP server.
It exists to do one thing well: let a program, or an agent, ask precise questions about how data and control move through real source code. Things like who calls this function, what reaches this sink, which sibling function guards this input, and what flows into here. And it answers them with compiler-level fidelity instead of regex or heuristic matching.
Why Lachesis exists
Most code-graph tools stop at symbols and references. That is the SCIP and LSIF layer, and it is useful, but it can only tell you where a name is used. It cannot tell you how a value moves.
Lachesis's whole point is the dataflow tier, because those are the edges that actually matter when you are reasoning about security:
VALUE_FLOWS_TOfor value and def-use flowPOINTS_TOfor points-to and pointer analysisTAINT_FLOWS_TOfor taint propagation from a source to a sinkALIASESfor aliasing relationshipsCALLS,MAY_INVOKE, andINVOKESfor resolved and possible call edges
A symbol index cannot give you any of these. They are what let a downstream tool reason about reachability, guard coverage, and tainted flows, rather than just "where does this name appear."
Quick start
git clone https://github.com/UnboundCompute/lachesis && cd lachesis
python -m pip install --upgrade pip # editable installs need pip >= 21.3
pip install -e . # the graph builder, the nav layer and the MCP server
npm install # the TypeScript compiler the TS frontend loads
Python 3.10 or newer is required. The only runtime dependencies are kuzu and
pyarrow, which back the embedded columnar store the graph lives in; the builder,
the navigation layer and the MCP server are otherwise pure standard library.
Then build a graph and ask it questions:
lachesis-analyze path/to/your/source graph.kuzu # parse a tree into a layered graph
lachesis-query graph.kuzu overview # what's in it
lachesis-query graph.kuzu function handleRequest # a budgeted slice of one function
lachesis-mcp graph.kuzu # serve the nav tools over MCP (stdio)
graph.kuzu is a directory: an embedded Kùzu database plus the store manifest. It
is the graph, and every tool reads it directly. lachesis-mcp speaks MCP over
stdio, so point an MCP-capable client at lachesis-mcp /abs/path/to/graph.kuzu and
the navigation tools show up as tools.
The build writes the core tier. The dataflow tier is f(core graph, languages, capabilities) — pure and deterministic — so it is rebuilt on the first query and
cached in a sibling graph.kuzu.enriched directory keyed to the core's content hash.
Answers are identical either way; the work moves off every build and onto one first
query per graph. lachesis-analyze --enrich folds it in at build time instead.
Parallel builds for monorepos
lachesis-analyze --parallel-packages splits a workspace by package (every
directory holding a package.json outside node_modules, deepest one wins) and
builds the packages in a process pool. --max-workers N caps the pool; it
defaults to the number of packages or the core count, whichever is smaller.
It is opt-in because it is a real semantic change, not just a scheduling one.
Each package becomes its own compiler program, so types resolve across that
package's files rather than across the whole tree. On the two-package workspace
fixture in this repo, the per-package build recovers 647 of the whole-repo
build's 650 edges, with an identical node set and zero invented edges. The three
it misses are cross-program: two macro expansions into lib.es5.d.ts and one
value flow across the package boundary. Edges whose far endpoint lands in a
different unit are dropped rather than guessed at, and the count is always
printed:
Dropped 0 cross-package edges (parallel build)
Wall time here is floored by the largest single package, so this does not scale
linearly with worker count: a workspace whose weight sits in one big package
gains almost nothing. --parallel-packages cannot be combined with
--incremental, whose manifest keys bundles by frontend rather than by package.
Languages
Three frontends ship in the repo. Each one is backed by a real compiler or by the language's own parser, never by a regex or a heuristic grammar.
| language | frontend | engine | extensions |
|---|---|---|---|
| TypeScript, JavaScript | typescript-compiler-api |
the TypeScript compiler API, with the type checker | .ts .tsx .mts .cts .js .jsx |
| C | clang-c |
clang -Xclang -ast-dump=json |
.c .h |
| Python | cpython-ast |
CPython's own ast and symtable, standard library only |
.py .pyi |
A frontend declares what it actually knows in its snapshot manifest, and the
validator holds it to that. Two honest limits worth stating up front: the Python
frontend has no type checker, so it reports types: none and resolves attribute
calls lexically rather than by type; and the C frontend reads a single translation
unit at a time, so it does not follow a call through a function pointer table it
never sees.
Mixed-language trees are one graph, not three. run_project picks a frontend per
file by extension, composes the snapshots into a single node and edge set, and
runs the same overlays over the result, so a Python caller and a TypeScript callee
sit in one store and the same navigation tools answer over both.
See it work
Before you point it at your own code, watch the dataflow tier catch something on
a project that ships in the repo. examples/README.md is
a five-minute walkthrough: build a graph from the bundled fixture, then watch
Lachesis tell two sibling functions apart because one authorizes a database
lookup and the other reaches the identical call with no check. That is the kind
of question a symbol index cannot answer, and it is the whole reason the dataflow
tier exists.
How it fits together
source tree
|
v
Lachesis (builder) language frontends parse each ecosystem and emit
| syntax + symbols + calls + dataflow overlays
| layered graph
v
kuzu_store bulk COPY-FROM staged Parquet writer into an
| embedded columnar graph DB (typed node/rel tables)
v
nav (+ MCP) graph_store, reachability, hubs, guards, call_roles,
siblings, flow, symbol_index, and an MCP server
Lachesis/, the graph builder
pipeline.pyorchestrates project partitioning and the per-frontend runs.frontends/holds the language frontends. Each one is parser or compiler backed and emits the graph.core/,types.py,ecosystems/,projections/, andreasoning/are the graph core, the node and edge types, ecosystem handling, projections, and the analysis overlays.kuzu_store.pyis the bulk writer. It stages the graph to Parquet and copies it into a Kùzu database using typed hot-relation tables plus a cold generic edge table. TheKUZU_STORE_SPEC.mdhas the full layout.cli/holds the command-line entry points for build, analyze, and export.
nav/, navigation and MCP
graph_store.pyloads a graph from either JSON or a Kùzu directory. It auto-detects which one it is looking at and gives you one API over both.kuzu_index.pyis the Kùzu-backed graph index.reachability.py,hubs.py,guards.py,call_roles.py,siblings.py,flow.py, andsymbol_index.pyare the reasoning primitives.mcp_server.pyexposes the navigation tools over MCP, so an LLM agent can drive the graph directly.
Benchmarks
All numbers below come from one public, reproducible target: the TypeScript
packages in the vercel/ai monorepo (ai@7.0.55),
built with the TypeScript frontend on an Apple M4 (16 GB), single process, Python
3.9, Kùzu 0.11.3. Clone the repo and point the analyzer at any package's src
directory to reproduce them.
Build throughput
Lachesis builds the full layered graph, including the dataflow tier, at roughly one thousand source lines per second, or ten to eleven thousand graph elements (nodes plus edges) per second, and it stays near-linear as the input grows.
Package (packages/<name>/src) |
TS LOC | Nodes | Edges | Build time | Serialized graph |
|---|---|---|---|---|---|
anthropic |
30,577 | 133,903 | 227,662 | 31.9 s | 265 MB |
openai |
44,890 | 210,164 | 361,406 | 49.6 s | 422 MB |
ai |
164,607 | 504,246 | 920,708 | 140.9 s | 1.0 GB |
python -m Lachesis.cli.analyze path/to/vercel-ai/packages/ai/src ai.kuzu
These build times and sizes were measured when the builder also wrote the whole
graph out as indented JSON, which it no longer does, and when it folded in the
dataflow tier on every build, which is now --enrich. The last column is that JSON
dump, kept here because it is the most direct measure of how much graph each
package produces. Both columns are therefore an upper bound on what the command
above costs today. The current numbers are not published here yet; they will be
once they have been re-measured on this same public target.
Storage and open time
The store is columnar and easy on RAM, which is the property that lets a
half-million-node graph open in under a second. On the ai graph above (504,246
nodes / 920,708 edges), against the one-big-JSON representation the builder used to
emit, loading each through the navigation layer:
| One-big-JSON | Kùzu store | change | |
|---|---|---|---|
| On-disk size | 1.0 GB | 368 MB | 63% smaller |
| Open time (load into nav) | 11.1 s | 0.58 s | about 19x faster |
| Load peak RSS | 3511 MB | 362 MB | 90% smaller |
| Warm query (hubs top-10) | about 1 ms | about 4 ms | parity |
That store was built with --prune, which drops the pure-lexical token and
source-span nodes. Pruning is lossless for every navigation tool (source excerpts
are read from the file by offset, not from those nodes) but it does drop real T0
graph content, so it is opt-in and the default store keeps everything.
The KUZU_STORE_SPEC.md covers the on-disk layout, the
incremental unit key, and the trade-offs we measured. The short version: columnar
scans give up a little warm-query latency in exchange for a large win on RAM and
startup time. A test suite enforces that the store answers every navigation and MCP
tool identically to the same graph held whole in memory.
Documentation
examples/README.mdis the five-minute walkthrough: build a graph and read a guard differential and a taint path out of it.docs/graph-model.mdis the reference for what the graph contains: the node kinds, the edge kinds, and the tiers, generated from the canonical contract.docs/queries.mdis the reference for asking the graph questions, both thelachesis-querycommand line and thelachesis-mcptools.KUZU_STORE_SPEC.mdcovers the embedded columnar store: the on-disk layout, the incremental unit key, and the trade-offs measured.
Status
Lachesis is early and moving fast. The graph model, the Kùzu store, and the navigation and MCP layer all work today, and they are covered by a parity test suite in Lachesis/frontends/checks.py.
There are known rough edges, and they live in the issue tracker. Two worth calling out: a tail-recursive control-flow walk can hit Python's recursion limit on very deep functions, and whole-repo multi-package builds currently need per-package compilation to stay inside a single Node process's heap.
License
Lachesis is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See LICENSE.
The short version: you are free to use, study, modify, and share it, including commercially. But if you run a modified version as a network service, you have to make your modified source available to the people using that service. That is the deal that keeps Lachesis and its improvements open.
If the AGPL does not fit your use case, say you want to embed Lachesis in a closed-source product, a separate commercial license may be available. See CONTRIBUTING.md for how licensing and contributions are handled, or open an issue to start the conversation.
Security
If you find a vulnerability, please do not open a public issue. See SECURITY.md for how to report it privately.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。