{
  "markdown": "<!-- mcp-name: io.github.UnboundCompute/lachesis -->\n\n# Lachesis\n\n**A compiler-precise code graph you can ask questions about: how data moves, who calls what, what reaches a sink. C, Python, and TypeScript, all in one graph.**\n\nInstall with `python -m pip install lachesis-cpg`, then use `import lachesis` or the `lachesis` command.\n\n[![PyPI](https://img.shields.io/pypi/v/lachesis-cpg)](https://pypi.org/project/lachesis-cpg/)\n[![Python](https://img.shields.io/pypi/pyversions/lachesis-cpg)](https://pypi.org/project/lachesis-cpg/)\n[![CI](https://github.com/UnboundCompute/lachesis/actions/workflows/ci.yml/badge.svg)](https://github.com/UnboundCompute/lachesis/actions/workflows/ci.yml)\n[![License: AGPL-3.0](https://img.shields.io/badge/license-AGPL--3.0-blue)](./LICENSE)\n[![MCP](https://img.shields.io/badge/MCP-server-1f6feb)](https://modelcontextprotocol.io)\n[![Docker](https://img.shields.io/badge/ghcr.io-lachesis-2496ED?logo=docker&logoColor=white)](https://github.com/UnboundCompute/lachesis/pkgs/container/lachesis)\n[![Glama](https://glama.ai/mcp/servers/UnboundCompute/lachesis/badges/score.svg)](https://glama.ai/mcp/servers/UnboundCompute/lachesis)\n[![Security Scan](https://img.shields.io/badge/security-Lachesis-8250df)](https://github.com/UnboundCompute/lachesis-action)\n\nA symbol index (LSP, ctags, SCIP) tells you *where a name appears*. Lachesis tells you\n*how a value moves* — does this request parameter reach that SQL call, which of these two\nnear-identical functions checks its input first, what can flow into this buffer. It parses\na codebase with real compilers, not regexes, builds one graph with a full dataflow layer\n(value-flow, points-to, taint, aliasing), and answers questions from that graph — on the\ncommand line, as a Python library, or over MCP to an AI agent.\n\n[![Lachesis flags SQL injection on a live pull request](docs/media/lachesis-demo-poster.png)](https://github.com/UnboundCompute/lachesis-action-demo/pull/5)\n\nA 55-second walkthrough: a Flask control plane where three handlers reach the same SQL sink\nunguarded while two siblings authorize first. Lachesis follows the value, flags the three,\nand names their guarded twins — [**see it live on the pull request →**](https://github.com/UnboundCompute/lachesis-action-demo/pull/5). Scan your own repo on every PR with the\n[Lachesis Security Scan Action](https://github.com/UnboundCompute/lachesis-action).\n\n## Quickstart\n\nInstall, then point it at a repo. One command builds and caches the graph and prints the\n**leads** — the reachable sensitive operations that no guard covers, each a question to\ninvestigate, not a verdict:\n\n```bash\npython -m pip install lachesis-cpg\nlachesis ./my-project\n```\n\n```\n  ✓ compiling (0.7s)\n  2,677 nodes, 4,539 edges from typescript-compiler-api\n  ✓ finding entrypoints that reach sensitive effects (0.1s)\n\n2 leads (lens=all)\n  1. [0.810] handleWebhook (http/webhook.ts:10, route) -> findById(documentId) [database]\n     prove or kill: a caller that passes no recognized guard can read or write data\n     through findById(documentId) starting from handleWebhook at http/webhook.ts:10\n  2. [0.810] handleWebhook (http/webhook.ts:10, route) -> findById(invoiceId) [database]\n     unknown: this function branches on something; an owner/tenant comparison would not\n     be recognized as a guard by name and is not modeled here\n```\n\nThat second lead is the point: `handleWebhook` reaches two near-identical database calls,\nand Lachesis tells them apart by *following the value*, not by matching a name. To hand the\nsame codebase to an agent that can chase these down, serve it over MCP:\n\n```bash\nlachesis mcp ./my-project        # zero-config: the agent builds and queries the graph itself\n```\n\nThe first run of a project is slow; graphs are cached under `~/.lachesis/cache` and every\nrun after is fast.\n\n## Three ways in: CLI, library, MCP\n\nThe same capability set is a command, a Python method, and an MCP tool — no surface is a\nsecond-class citizen, and none makes you hand-write a graph-loading script.\n\n**CLI** — one `lachesis` entrypoint. `scan` is the front door; when you want to name a\ngraph and drive it yourself, the verbs mirror the three build passes:\n\n```bash\nlachesis build   ./my-project graph.kuzu     # pass 1 — the structural graph\nlachesis enrich  graph.kuzu                   # pass 2 — warm the dataflow + catalog sidecars\nlachesis analyze graph.kuzu --summary         # pass 3 — the leads, rolled up by bug shape\nlachesis explain graph.kuzu tree.c:1487       # one call: the whole evidence chain for a site\n```\n\nFor a large tree, build core-only and cap the wall clock — each frontend shard streams\nstraight into Kùzu instead of composing a graph-sized Python object, and `enrich` reads\nthe sidecars this leaves behind rather than re-parsing the source:\n\n```bash\nlachesis build ./my-project graph.kuzu --prune --timeout 3600\n```\n\nOn a full libxml2 tree that cold build is ~28 s and ~1 GiB peak RSS across all three\nlanguages. The streaming layout, sidecar formats, and memory/timing knobs are in\n[`docs/scaling.md`](./docs/scaling.md).\n\n**Library** — a warm session: open (or build) once, ask many times, nothing recomputed\nbetween questions.\n\n```python\nimport lachesis\n\na = lachesis.Analysis.build(\"./my-project\", \"graph.kuzu\", enrich=True)\nleads = a.scan(hard_stop=120)                  # bounded scan → a LeadSet held in memory\nprint(leads.summary())                         # {'total': ..., 'by_pattern': {...}, 'timed_out': False}\n\nfor lead in leads.near(\"tree.c\", (1480, 1500)):   # filter the held leads, no recompute\n    print(lead.pattern, lead.entry, lead.line)\n\nprint(a.explain_sink(\"tree.c\", 1487))          # the whole evidence chain for one site\n```\n\n`scan` returns a `LeadSet` with `.summary()`, `.by_pattern()`, `.by_function()`,\n`.near()` / `.at()`, `.top()`, `.to_json()`, and typed iteration — the leads stay in the\nsession, so a follow-up question is a filter, not a second pass. Bounded by default: with no `hard_stop`\nit still caps its own wall clock and returns partial, flagged leads rather than hanging.\nRunnable one-file scripts for each operation are in [`examples/`](./examples/README.md).\n\n**MCP** — every verb above is also a tool an agent drives directly (`build_graph`,\n`enrich`, `flow_pass`, `explain`, and the in-memory `leads_*` queries) over the same warm\nsession. See [MCP](#mcp).\n\n## What you can ask\n\nOnce a graph is built, these are the moves — from the command line, the `Analysis` library,\nor as MCP tools an agent drives directly:\n\n| You want to know | The move |\n|---|---|\n| What is this subsystem built around? | `hubs`, the highest-degree functions (no name knowledge needed) |\n| Where is this symbol? | `search` |\n| Who calls this? What does it call? | `callers`, `callees` (direct and indirect dispatch) |\n| Show me the actual source | `read_body`, exact bytes by offset |\n| What's in this file or folder? | `open_file`, `open_folder` |\n| Where does this value go? What feeds this sink? | `flow`, `sources_of` |\n| Does this source reach that sink? | `reaches`, a labeled witness path or an honest \"no\" |\n| What does this pointer point to? What aliases it? | `points_to`, `aliases` |\n| Where does untrusted input reach a dangerous sink? | `taint`, source→sink witnesses folded from the Atropos catalog onto this graph's nodes |\n| Which entrypoints reach sensitive effects without a recognized guard? | `scan`, the leads with census/frontier counts (questions, not verdicts) |\n| What are the leads, and where do they land? | `analyze` / `leads_summary` / `leads_at`, held warm and filtered by pattern, function, or `file:line` |\n| The full evidence for one site, in one call | `explain`, chaining census → candidate → provenance → guard → source |\n\nEvery answer carries a confidence and an origin. An `exact` edge is resolved; a\n`conservative` one is a deliberate over-approximation the tool tells you about rather than\nhiding. You read the results as evidence, not as verdicts.\n\n## MCP\n\nUse `lachesis mcp` from the same environment that built the graph. You can hand it an\nabsolute `graph.kuzu` path, but you don't have to: start it with no argument and the agent\nbuilds its own graph on demand with `build_graph` — point it at a repo and it compiles,\ncaches, and attaches in one call (an unchanged tree is served from cache; `refresh: true`\nforces a rebuild). Overlapping requests are serialized around the single store, so a\nconcurrent call can't tear the server down mid-flight.\n\n**One click** (uses `uvx`, no install step):\n\n[![Add lachesis to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=lachesis&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJsYWNoZXNpcy1jcGciLCJsYWNoZXNpcyIsIm1jcCJdfQ==)\n&nbsp;\n[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Lachesis-0098FF?logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=lachesis&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22lachesis-cpg%22%2C%22lachesis%22%2C%22mcp%22%5D%7D)\n\nOr configure any client by hand — drop one of these into your MCP client's config\n(Claude Desktop, Cursor, Claude Code). If the package is already installed:\n\n```json\n{\n  \"mcpServers\": {\n    \"lachesis\": { \"command\": \"lachesis\", \"args\": [\"mcp\"] }\n  }\n}\n```\n\nOr with no install step, letting `uvx` fetch it on first run:\n\n```json\n{\n  \"mcpServers\": {\n    \"lachesis\": { \"command\": \"uvx\", \"args\": [\"--from\", \"lachesis-cpg\", \"lachesis\", \"mcp\"] }\n  }\n}\n```\n\nOr as a container — no Python, Node, or clang on the host, all three frontends in the image:\n\n```json\n{\n  \"mcpServers\": {\n    \"lachesis\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"--rm\", \"-i\", \"-v\", \"/path/to/your/project:/src\",\n               \"ghcr.io/unboundcompute/lachesis:edge\"]\n    }\n  }\n}\n```\n\nMount your project (here `/src`) and point `build_graph` at it. In VS Code use\n`${workspaceFolder}` for the mount source. The image is published for linux/amd64 and\nlinux/arm64; `:edge` tracks `main`, and each release also publishes an `:x.y.z` tag.\nMore client and troubleshooting notes are in\n[`docs/queries.md`](./docs/queries.md#the-lachesis-mcp-server).\n\n## Languages\n\nThree frontends, each backed by a real compiler or the language's own parser, never a\nheuristic grammar.\n\n| Language | Engine | Extensions |\n|---|---|---|\n| TypeScript / JavaScript | the TypeScript compiler API, with the type checker | `.ts` `.tsx` `.mts` `.cts` `.js` `.jsx` |\n| Python | CPython's own `ast` + `symtable` (standard library only) | `.py` `.pyi` |\n| C | Clang, via its AST dump | `.c` `.h` |\n\nA mixed tree is **one graph, not three**. Lachesis picks a frontend per file, composes the\nresults into a single node and edge set, and runs the same analysis over all of it — a\nPython caller and a TypeScript callee sit in the same store and the same tools answer over\nboth.\n\nTwo honest limits, stated up front: Python has no type checker, so it resolves attribute\ncalls lexically and says so (`types: none`); C reads one translation unit at a time, so it\nwon't follow a call through a function-pointer table it never sees. Each frontend declares\nwhat it actually knows, and a validator holds it to that claim.\n\n## How it's built\n\nLachesis works in three passes, and each is a verb.\n\n**Pass 1 — `build`** parses the source with real compilers into the *core tier*: syntax,\nsymbols, and calls. This is the fast part, and all most navigation needs.\n\n**Pass 2 — `enrich`** materializes the *dataflow tier* — value-flow, points-to, taint,\naliasing — a pure function of the core graph, so it is never written at build time. You\nrarely run it by hand: any query that needs value-flow folds in just the *cone* around its\nseed and caches it beside the store, so nothing pays for a whole-graph pass it never asked\nabout. `enrich` is the one-shot \"warm it all now\" for a batch job, persisting the tier and\ncatalog bind as `.dataflow.pb` / `.bind.pb` sidecars so a later, fresh process opens warm.\n\n**Pass 3 — `analyze`** runs the flow pass over the enriched graph and produces the leads:\nsafety-obligation sites, scored and matched against bug shapes. It is **bounded** — a\n`hard_stop` budget caps the wall clock and returns partial leads with `timed_out=True`\nrather than hanging, so a large graph can't stall a call. An empty result over a partial\nrun reads as *not evaluated*, never *clean*.\n\n```\n  source tree\n      |\n      v  build  (pass 1)\n  frontends        real compilers parse each language into\n      |            syntax, symbols, calls  (the core tier)\n      v  enrich (pass 2, on demand or all-at-once)\n  kuzu store       staged Parquet, bulk-copied into an embedded\n      |            columnar graph DB; dataflow tier folded in as a\n      |            cone around each seed, cached beside the store\n      v  analyze (pass 3, bounded)\n  nav  (+ MCP)     hubs, search, callers/callees, read_body, flow,\n                   reaches, sources_of, points_to, aliases, scan,\n                   explain, leads — over one warm session\n```\n\n`graph.kuzu` is a directory: the embedded database plus a manifest. That *is* the graph.\nEvery tool reads it directly, and `lachesis mcp` serves the same tools over stdio for any\nMCP-capable client. Large-build, monorepo, and CI tuning — including cold-build memory and\ntiming on a full libxml2 graph — live in [`docs/scaling.md`](./docs/scaling.md); the graph\nmodel is in [`docs/graph-model.md`](./docs/graph-model.md).\n\n## Install\n\n```bash\npython -m pip install lachesis-cpg\n```\n\nThe release-tested Python window is 3.10–3.12 (the CI matrix). Python analysis needs\nnothing beyond the package; TypeScript/JavaScript builds need `node` on `PATH` and C\nbuilds need `clang` — a missing one comes back as an actionable error, not a crash.\n\nTo work from a clone (the contributor workflow, and how you build the TypeScript frontend\nfrom checked-out sources):\n\n```bash\ngit clone https://github.com/UnboundCompute/lachesis && cd lachesis\npython -m pip install --upgrade pip     # editable installs need pip >= 21.3\npython -m pip install -e \".[dev]\"       # builder, nav, MCP server, tests\nnpm ci                                   # install the locked TypeScript compiler dependency\ncargo build --release --manifest-path native/clang_frontend/Cargo.toml\n```\n\nRuntime dependencies are just `kuzu` and `pyarrow`; everything else is standard library.\nNode 20+ must be on your PATH for the TS frontend. In a source checkout, the C frontend\nautomatically uses the release Rust binary above; without that binary it uses the\nportable Clang frontend. Run the frontend parity gate CI uses with `make check`.\nSemantic `concept_search` is optional and separate — opt in with\n`pip install -e \".[concept-search]\"`, then `lachesis concept-model download`.\n\n## Where to go next\n\n- **[`examples/`](./examples/README.md)**: a five-minute walkthrough on a bundled fixture, plus one runnable `.py` script per library operation.\n- **[`docs/graph-model.md`](./docs/graph-model.md)**: what's in the graph — node kinds, edge kinds, and tiers.\n- **[`docs/queries.md`](./docs/queries.md)**: every way to ask a question, both `lachesis query` and the MCP tools.\n- **[`docs/scaling.md`](./docs/scaling.md)**: large-build, monorepo, and CI-runner tuning; managing the local graph cache.\n\n## Roadmap\n\nRecently shipped:\n\n- [x] **One reader, three front doors.** The `lachesis.Analysis` library class is the single implementation; a `lachesis <verb>` subcommand and an MCP tool sit over each method — no hand-written graph-loading script on any surface.\n- [x] **Bounded analysis.** Pass 3 takes a `hard_stop` budget and returns partial, flagged leads instead of hanging; the census a graph pays for once is cached as a sidecar so the next process opens warm.\n- [x] **Zero-config MCP.** `lachesis mcp` starts with no graph path; `build_graph` compiles, caches, and attaches on demand, and overlapping requests are serialized around the store.\n- [x] **A smaller front door.** One default command (`lachesis <path>`), one result noun everywhere (`lead`), and a five-name library API (`scan`, `Analysis`, `LeadSet`, `Deadline`, `AnalysisError`) — so the first command and the first import are obvious.\n\nNear-term, roughly in order:\n\n- [ ] **Monorepo-scale builds.** `--parallel-packages` compiles each package on its own so very large TypeScript trees don't exceed the compiler's internal limits; making that the smooth default is active work.\n- [ ] **Bounded security signal.** Reworking the guard-analysis tools to fold the same per-seed, on-demand cone the dataflow tools already use, so they run on a large graph without a whole-graph pass.\n- [ ] **The reachability query, first-class.** \"Can attacker input reach this sink\" as a single call returning a witness path or a bounded no, across file, package, and language boundaries.\n\n## Status\n\nLachesis is early and moving fast. The graph model, the store, and the navigation and MCP\nlayer work today and are held to a parity test suite that checks the columnar store answers\nevery tool identically to the same graph held whole in memory. The schema and tool set may\nstill shift before 1.0; the [`CHANGELOG`](./CHANGELOG.md) calls out changes explicitly.\n\n## License\n\nAGPL-3.0. See [`LICENSE`](./LICENSE). You're free to use, study, modify, and share it,\ncommercially included; run a modified version as a network service and you make your\nmodified source available to its users. If that doesn't fit — say, embedding in a closed\nproduct — a separate commercial license may be available. See\n[`CONTRIBUTING.md`](./CONTRIBUTING.md) or open an issue.\n\n## Security\n\nFound a vulnerability? Please don't open a public issue; see [`SECURITY.md`](./SECURITY.md)\nfor private reporting.\n",
  "bytes": 17730,
  "sha": "7778779bee2f8f0e3e6b259bd9db05eef827f17c7af3226eb22138e920c8547b",
  "repo_slug": "unboundcompute/lachesis",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_unboundcompute_lachesis_51034e4d/readme"
}