Local code graph analyzer. Parses Rust, Python, JavaScript, and TypeScript source files into a directed call graph using tree-sitter, then serves that graph over the Model Context Protocol (MCP) via stdio. Any MCP client — Claude Code, Cursor, Kiro, or others — can query callers, callees, impact chains, and codebase statistics without leaving the editor.
Requires a Rust toolchain (1.70+) and a C compiler for tree-sitter grammars.
cargo build --release
For git diff analysis (changed-function detection, PR review), enable the
git-support feature. This pulls in git2, which links against OpenSSL:
cargo build --release --features git-support
If the OpenSSL build fails, see TROUBLESHOOTING.md.
The binary lands at target/release/graph_reviewer.
graph_reviewer <COMMAND> [OPTIONS]
index — Parse a codebase and print graph statistics.
graph_reviewer index --path /path/to/code
mcp — Start the MCP server. By default, communicates over stdio
(stdin/stdout). With --port, starts an HTTP server with SSE streaming
instead.
graph_reviewer mcp -P /path/to/code [--git-dir /path/to/repo]
graph_reviewer mcp -P /path/to/code --port 3000
-P is the directory to index. --git-dir points to the git repository root
(containing .git/); required for the analyze_diff and review_changes
tools.
Without --port, the server reads JSON-RPC from stdin and writes to stdout.
An MCP client spawns this process as a subprocess.
With --port, the server binds to 127.0.0.1:<port>/mcp and accepts HTTP
POST requests. Responses stream back as Server-Sent Events (SSE). The server
prints the listening URL to stderr on startup.
query — Run a one-off query from the command line (useful for testing).
graph_reviewer query -P ./my-project --query-type callers --target "authenticate"
graph_reviewer query -P ./my-project --query-type callees --target "main"
graph_reviewer query -P ./my-project --query-type similar --target "process_data"
export — Export the graph to JSON, CSV, or SQL.
graph_reviewer export -P ./my-project -o graph.json -f json
graph_reviewer export -P ./my-project -o graph.sql -f sql
pr-export — Export a subgraph focused on changed functions between two git
refs (requires --features git-support).
graph_reviewer pr-export -P . -o pr.sql --from-ref main --to-ref HEAD --hops 2
diff-export — Export the full graph with prefixed SQL table names, for side-by-side comparison of two snapshots.
graph_reviewer diff-export -P . -o before.sql --prefix before_
graphReviewer supports two transports:
- stdio (default) — the MCP client spawns the binary as a subprocess and talks JSON-RPC over stdin/stdout. Use this for local IDE integration.
- HTTP SSE (
--port) — the binary runs as a standalone HTTP server. The client connects tohttp://127.0.0.1:<port>/mcpvia POST and receives Server-Sent Events. Use this for shared servers or clients that prefer HTTP.
Global config (~/.claude/.mcp.json) — applies to every project:
{
"mcpServers": {
"graphReviewer": {
"command": "/path/to/graph_reviewer",
"args": ["mcp", "-P", "."],
"env": {}
}
}
}Or use a wrapper script that auto-detects the repo root:
#!/usr/bin/env bash
set -euo pipefail
BINARY="$HOME/workspace/graphReviewer/target/release/graph_reviewer"
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || {
echo "Error: not inside a git repository" >&2
exit 1
}
exec "$BINARY" mcp -P "$REPO_ROOT" --git-dir "$REPO_ROOT"Per-project config (.mcp.json in project root):
{
"mcpServers": {
"graphReviewer": {
"command": "/absolute/path/to/graph_reviewer",
"args": ["mcp", "-P", "/absolute/path/to/project", "--git-dir", "/absolute/path/to/project"]
}
}
}Add to .cursor/mcp.json in your project, or ~/.cursor/mcp.json globally:
{
"mcpServers": {
"graphReviewer": {
"command": "/path/to/graph_reviewer",
"args": ["mcp", "-P", "/path/to/project", "--git-dir", "/path/to/project"]
}
}
}Add to .kiro/mcp.json in your project root:
{
"mcpServers": {
"graphReviewer": {
"command": "/path/to/graph_reviewer",
"args": ["mcp", "-P", "/path/to/project", "--git-dir", "/path/to/project"]
}
}
}Start the server first, then point your client at the URL:
graph_reviewer mcp -P /path/to/project --git-dir /path/to/project --port 3000
The server prints MCP HTTP server listening on http://127.0.0.1:3000/mcp to
stderr. Configure your client with:
{
"mcpServers": {
"graphReviewer": {
"url": "http://127.0.0.1:3000/mcp"
}
}
}stdio — pipe an initialize request into the binary:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' \
| graph_reviewer mcp -P .HTTP — start the server, then curl it:
graph_reviewer mcp -P . --port 3000 &
curl -X POST http://127.0.0.1:3000/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'Both should return a JSON-RPC response with serverInfo and capabilities.
The server exposes 10 tools. The first 8 work on any codebase; the last 2
require the git-support feature and --git-dir.
Find every function that calls the target. Returns a markdown table with qualified name, file path, line range, and signature.
{"function": "authenticate"}Find every function the target calls. Same output format as find_callers.
{"function": "main"}Walk the call graph upward from the target to find all direct and transitive callers. Reports affected files and a risk level (Low / Medium / High / Critical) based on caller count and file spread.
{"function": "validate_token", "depth": 5}depth is optional (default 3, max 10). Depth 1 gives direct callers only.
Case-insensitive substring search across all function qualified names. Optionally filter by language.
{"query": "handle", "language": "rust"}language accepts rust, python, javascript, typescript.
Returns file counts, function counts, and class counts broken down by language, plus the top 10 "hotspot" functions (most callers). Takes no arguments.
Find functions with similar call patterns to the reference function, using Jaccard similarity on callee sets. Returns the top 10 matches above 30%.
{"reference_function": "process_data"}General-purpose query — dispatches to callers, callees, or similar_functions
based on the query_type parameter.
{"query_type": "callers", "target": "main"}Re-parse the codebase and rebuild the graph. Call this after making code changes so subsequent queries reflect the current state. Takes no arguments.
Requires git-support. Identify which functions changed between two commits and run impact analysis on each.
{"from_commit": "main", "to_commit": "HEAD"}Requires git-support. Full PR review: lists changed functions with line numbers, runs impact analysis, and optionally includes source context with changed lines marked.
{"from_ref": "main", "to_ref": "HEAD", "include_context": true, "impact_depth": 3}include_context (default true) and impact_depth (default 3) are optional.
Resources provide read-only data about the indexed graph. Clients can list and read these via the standard MCP resource protocol.
| URI | Returns |
|---|---|
graph://stats |
File, function, and class counts by language; top hotspots (JSON) |
graph://files |
Every indexed file with path, language, and line count (JSON array) |
graph://functions |
Every indexed function with qualified name, file, lines, signature (JSON array) |
graph://hotspots |
Top 10 functions by caller count (JSON array) |
graph://functions/{name} |
Single function detail: signature, location, callers, callees (JSON) |
Prompts provide pre-built context that MCP clients can inject into LLM conversations.
| Name | Arguments | Description |
|---|---|---|
analyze-function |
function (required) |
Function signature, callers, callees, and impact assessment |
codebase-overview |
(none) | Statistics-based architectural overview |
review-pr |
from_ref, to_ref, include_context, impact_depth |
Changed functions with impact for PR review (requires git-support) |
Source files (Rust, Python, JS, TS)
│
▼
TreeSitterParser ──▶ extracts functions, classes, imports, call sites
│ resolves qualified names (Type::method, Class.method)
▼
CodeGraph (petgraph directed graph)
│ file_index: HashMap<PathBuf, NodeIndex>
│ function_index: HashMap<String, Vec<NodeIndex>>
│ class_index: HashMap<String, Vec<NodeIndex>>
▼
MCP Server (rmcp SDK, stdio transport)
│ lazy init: graph built on first tool call
│ all tools, resources, prompts served inline
▼
MCP Client
| Language | Extensions | Constructs |
|---|---|---|
| Rust | .rs |
Functions, impl blocks → Type::method, structs, enums, use, call expressions |
| Python | .py |
Functions, classes → Class.method, self.method() resolution, imports, calls |
| JavaScript | .js, .jsx |
Functions, arrow functions (named from variable declarator), classes, imports, calls |
| TypeScript | .ts, .tsx |
Same as JavaScript |
Hardcoded skip list: .git, node_modules, .venv, venv, env,
__pycache__, target, dist, build, out, .next, cdk.out,
coverage, .coverage, htmlcov, .pytest_cache, .mypy_cache
Additionally reads .gitignore in the project root and filters matching
directory/file names (simple name matching, not full glob).
Nodes: File, Function, Class, Variable, Import
Edges: Calls, Defines, Imports, Uses, Inherits
Variable nodes and Uses/Inherits edges are defined in the type system but not currently created by the parser.
The export and pr-export commands can produce SQL files loadable into
DuckDB for ad-hoc analysis. See scripts/ for review queries and prompts:
scripts/
review.sh # Run codebase review queries
pr-review.sh # Run PR-focused review queries
ingest.sh # Load SQL export into DuckDB
snapshot-export.sh # Export graph snapshots for diff
review_queries.sql # Codebase health queries
pr_review_queries.sql # PR review queries
diff_queries.sql # Snapshot comparison queries
REVIEW_PROMPT.md # LLM prompt for codebase review
PR_REVIEW_PROMPT.md # LLM prompt for PR review
cargo test
113 tests across 11 suites (without git-support; git tests add 7 more):
| Suite | Tests | Covers |
|---|---|---|
parser_qualified_names |
8 | Qualified names: Rust impl, Python class, JS/TS class, arrow functions |
parser_edge_cases |
9 | Nested classes, decorators, closures, multiline signatures |
parser_gitignore |
6 | Directory filtering, .gitignore parsing |
graph_queries |
18 | Callers, callees, impact, similarity, search, stats algorithms |
export_integration |
21 | JSON, CSV, SQL export roundtrips |
cli_export |
4 | CLI export subcommand |
mcp_protocol |
29 | MCP protocol: initialize, tools, resources, prompts (subprocess) |
mcp_handlers |
15 | Tool handler unit tests |
mcp_http |
3 | HTTP SSE transport: initialize, tools/list, tool call (subprocess) |
git_diff_line_level |
3 | Line-level diff, function overlap detection (git-support) |
code_review_integration |
4 | End-to-end review_changes (git-support) |
| Crate | What for |
|---|---|
petgraph |
Directed graph |
tree-sitter + language grammars |
AST parsing |
rmcp |
MCP protocol (server, stdio transport, JSON schema) |
serde / serde_json |
Serialization |
tokio |
Async runtime |
clap |
CLI |
git2 (optional) |
Git integration via libgit2 |
walkdir |
Filesystem traversal |
csv / chrono |
Export |
anyhow / thiserror |
Errors |
tracing / tracing-subscriber |
Logging (stderr, RUST_LOG=debug) |
- Name-based call resolution:
obj.method()matches bare function names in the global index — false edges when classes share method names - No import tracing: Calls matched by name, not by following import paths
- Single-threaded parsing: Files parsed sequentially
- In-memory graph: Rebuilt on every server start, no persistence
- Basic
.gitignore: Name-component matching only, no nested files, no full glob - No TS-specific constructs: Interfaces, enums, namespaces, generics not tracked
- Python decorators: Not tracked
find_patternsis O(n²): Compares every function pair; slow on large codebases
MIT