code-graph
Enables querying cross-repo code dependencies, HTTP routes, database tables, and queues via an MCP server using Cypher queries.
README
code-graph
A static connection graph of the Example platform. It scans app-web
(Laravel + React) and app-services (NestJS + Go + Python) and records how the
code is wired together — which files call which HTTP routes, which tables and
queues they read or write, and what they import — into an embedded
Kùzu graph database you can query from the terminal, from
Claude Code (MCP), or with raw Cypher.
It answers the cross-repo reachability questions that are otherwise a multi-file grep-and-guess exercise:
- Who actually calls
GET /resource-a? - What code touches the
table_btable? - How does the web frontend reach a backend service?
Quickstart
uv sync # install (Python 3.11–3.13)
uv run code-graph scan # build graph.kuzu from the sibling repos
uv run code-graph stats # node + per-edge-type counts
uv run code-graph query "callers:/resource-a" # who calls a route
By default scan looks for app-web and app-services as siblings of this
project (../app-web, ../app-services) and reads route data from
../route-inventory/dist/openapi.json. Point it at your own repos with env vars
instead of editing the code:
export CODE_GRAPH_REPOS="/path/to/web-repo,/path/to/services-repo" # or name=path pairs
export CODE_GRAPH_OPENAPI="/path/to/route-inventory/dist/openapi.json"
Missing repos are skipped, not errored. The scan is a static snapshot — re-run it after any change you want reflected. A full scan of two large repos takes roughly 15–20 minutes.
Each scan writes two gitignored artifacts at the project root: graph.kuzu
(the database) and parser-report.txt (scan counts).
The three interfaces
CLI
uv run code-graph scan
uv run code-graph stats
uv run code-graph query "<preset-or-cypher>"
uv run code-graph export --format html|graphml|dot
query takes one argument. If it looks like <preset>:<arg> it runs a preset;
otherwise it runs as raw Cypher.
| Preset | Finds |
|---|---|
callers:<route> |
files that issue an HTTP call to a matching route |
calls:<node> |
outbound edges from matching nodes |
touches:<table|queue> |
code that reads/writes a table or uses a queue |
neighbors:<node> |
one-hop neighbours of matching nodes |
search:<text> |
nodes whose id/name contains the text |
export writes graph.{html,graphml,dot} (all gitignored, regenerated on
demand). graph.html is a self-contained offline viewer; GraphML/DOT are for
external graph tools.
MCP (in Claude Code)
CODE_GRAPH_DB=/home/ohad/sh/projects/code-graph/graph.kuzu uv run code-graph-mcp
Exposes the query layer over stdio as seven read-only tools: find_callers,
what_calls, path_between, who_touches, neighbors, search_nodes,
run_cypher. The server refuses to start until graph.kuzu exists — run scan
first.
Raw Cypher
The CLI query command (when the argument isn't a preset) and the MCP
run_cypher tool run arbitrary Cypher against the model below. The database is
opened read-only at the Kùzu engine level, and run_cypher additionally
rejects write/DDL keywords before executing — the graph cannot be mutated
through either path.
uv run code-graph query "MATCH (f)-[c:HTTP_CALL]->(r) WHERE r.kind='route' RETURN f.id, r.id LIMIT 20"
Graph model
One Node table (discriminated by kind) and one relationship table per edge
type — the idiomatic Kùzu shape, so filtering is a kind predicate and every
traversal is a single relationship type.
Node kind |
Represents | Example id |
|---|---|---|
file |
a source file | file:app-web/app-frontend/src/components/callRecord/StatusCheck.jsx |
route |
an HTTP route (seeded from route-inventory) | route:service-b:GET:/system/health-check-full |
db_table |
a database table | db_table:table_b |
queue |
a Redis stream / channel | queue:service-c-stream |
package |
an imported package/module | package:@nestjs/common |
external |
an HTTP call that didn't resolve to a known route | external:https://example.com/x |
| Edge type | Meaning | confidence |
|---|---|---|
HTTP_CALL |
file → route it calls (or external if unresolved) |
0.8 resolved / 0.5 external |
DB_ACCESS |
file → table it reads/writes (Laravel $table/DB::table, Kysely) |
0.7 |
QUEUE_PUB / QUEUE_SUB |
file → queue/stream it publishes/consumes | 0.6 |
IMPORTS |
file → package or file it imports | 0.9 |
Every edge carries source_file, line, and raw (the matched snippet), plus
mode (read/write/unknown) on DB_ACCESS. confidence describes how the
match was made, not a statistical accuracy — treat low-confidence and
external edges as leads to verify against source_file:line, not ground truth.
How it works
- Parsing. tree-sitter plus targeted regex over PHP, TS/TSX, JS/JSX, Go, and Python. No type resolution or control-flow analysis, and deliberately recall-biased — it favours catching real edges over avoiding every false positive.
- Routes come from route-inventory. Route nodes aren't discovered by parsing;
they're loaded from
route-inventory's generateddist/openapi.json.HTTP_CALLedges resolve a call's(METHOD, path)against that catalog (with a suffix-match fallback and same-origin tie-break); unmatched calls becomeexternalnodes. - What's scanned. Both repos are walked recursively for the source
extensions above, excluding
node_modules,vendor,dist,build,.git,.claude(covers.claude/worktrees),.next,coverage. Symlinked directories are followed once, guarded by resolved real path.
Example output
From a scan of the real app-web + app-services trees (2026-07-16):
4248 files scanned → 10108 nodes, 0 parse errors
nodes: file 6653 · package 1594 · route 1559 · db_table 261 · external 41
edges: IMPORTS 21212 · DB_ACCESS 794 · HTTP_CALL 189 · QUEUE 0 (22195 total)
$ uv run code-graph query "callers:/system/health-check-full"
[
{
"caller": "file:app-web/app-frontend/src/components/callRecord/StatusCheck.jsx",
"route": "route:service-b:GET:/system/health-check-full",
"line": 168,
"confidence": 0.8
}
]
IMPORTS dwarfs the other edge types by design — one edge per import statement
across thousands of files, versus per-call-site matches elsewhere.
Known limitations
- Minified build bundles add noise.
app-web/public/js/*hashed Vite/webpack output is currently scanned, producing some spuriousHTTP_CALLedges and file nodes. Excluding build output at the walker level is a planned follow-up. - Relative imports aren't resolved. A relative specifier (
./x,../y) becomes a literalfile:node id rather than being resolved to the real file it points at, so a share offilenodes are synthetic import targets. Bare package imports resolve correctly. DB_ACCESS.modeisunknownfor PHP. Only the TS/Kysely path (selectFrom/insertInto/…) infers read vs. write; Laravel$table/DB::tablehits have no per-call-site signal, somodeis leftunknownrather than guessed.- Queues need string literals.
QUEUE_*edges are only created for string-literal stream/channel names; Kafka-stylesubscribe(variable)calls aren't named. (QUEUE = 0on the current tree is accurate, not a miss.)
Development
uv run pytest # 42 tests
src/code_graph/
cli.py CLI entry point (scan / stats / query / export)
mcp_server.py MCP entry point (code-graph-mcp)
config.py ScanConfig: sibling-repo paths, exclude list, db path
model.py Node/Edge dataclasses + stable id helpers
catalog.py RouteCatalog: loads route-inventory OpenAPI, resolves calls
walker.py source-file walker (language detection, excludes, symlink guard)
parsing.py tree-sitter parse cache + AST helpers
matchers/ HTTP_CALL / DB_ACCESS / QUEUE_PUB+SUB / IMPORTS extraction
graph/
loader.py Kùzu schema + CSV bulk loader
queries.py GraphDB: read-only presets + guarded raw-Cypher escape hatch
scan.py orchestrates walker → matchers → loader, writes parser-report.txt
report.py parser-report.txt writer
skill/SKILL.md Claude Code skill definition
docs/superpowers/ design spec + implementation plan
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.