CallGraph

NicolasVautrin/CallGraph

3.2

If you are the rightful owner of CallGraph and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.

The CallGraph MCP Server is designed for analyzing the call graph of Axelor projects, enabling intelligent queries about method and class usage, impact analysis, and dependency navigation.

Tools
8
Resources
0
Prompts
0

CallGraph MCP Server

MCP server for analyzing call graphs in Axelor projects using ASM bytecode analysis.

What is this?

A Model Context Protocol (MCP) server that lets Claude Code intelligently query your project's call graph:

  • Find where methods/classes are used
  • Analyze change impact
  • Navigate dependencies
  • Trace call chains across Java bytecode

Key Features:

  • ASM bytecode analysis instead of source parsing (100% accurate)
  • SQLite relational database instead of vector database
  • Gradle dependency discovery for automatic Axelor package detection
  • Smart caching with SHA256-based invalidation

Quick Start

1. Install dependencies

pip install -r requirements.txt

2. Configure Claude Code

Add to your .claude/mcp.json or claude_desktop_config.json:

Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "sqlite-callgraph": {
      "command": "uvx",
      "args": ["mcp-server-sqlite", "--db-path", "C:/path/to/project/.callgraph.db"]
    }
  }
}

3. Extract your project's call graph

cd /path/to/your/axelor/project

# Incremental extraction (default, uses cache)
python /path/to/CallGraph/run_asm_extraction.py .

# Full reset (first time or after schema changes)
python /path/to/CallGraph/run_asm_extraction.py . --init

# Limit extraction for testing (requires --init)
python /path/to/CallGraph/run_asm_extraction.py . --init --limit 100

This creates .callgraph.db in your project directory.

Modes:

  • Default (Incremental): Smart caching - only re-extracts modified packages
  • --init: Full reset - drops all tables and rebuilds from scratch

4. Restart Claude Desktop

The MCP server provides direct SQLite access to the call graph database.

Architecture Overview

Project (.callgraph.db SQLite database)
    ↓ Extraction via
run_asm_extraction.py
    ├─> GradleDependencyManager   # Auto-discover Axelor deps
    └─> ASMExtractor               # Python client
        ↓ REST API
    ASMAnalysisService (Java)      # Port 8766
        ↓ ASM ClassVisitor
    Bytecode Analysis (.class files)
        ↓ Storage
    SQLite Tables:
    ├─> symbol_index   # FQN → URI → package
    ├─> nodes          # classes, methods
    └─> edges          # calls, inheritance, member_of

Components

1. run_asm_extraction.py

CLI entry point for the extraction pipeline.

Usage:

# Incremental extraction (default, uses cache)
python run_asm_extraction.py /path/to/project

# Full reset (first time or after schema changes)
python run_asm_extraction.py /path/to/project --init

# Limit extraction for testing (requires --init to avoid partial data)
python run_asm_extraction.py /path/to/project --init --limit 100

Modes:

  • Incremental (default): Uses SHA256 caching - only re-extracts modified packages
  • --init: Full reset - drops all tables and rebuilds from scratch

Process (always runs both steps):

  1. Package Discovery via GradleDependencyManager (Gradle)
  2. Symbol Indexing via ASMExtractor.build_symbol_index() (FQN → URI)
  3. Call Graph Extraction via ASMExtractor.extract() (Nodes + Edges)

2. GradleDependencyManager.py

Discovers and manages Axelor dependencies via Gradle.

Features:

  • Query Gradle for runtime JAR dependencies (Axelor only)
  • Extract JARs to axelor-repos/ cache directory
  • Provide package metadata (group, artifact, version, jar, sources, classes)

Cache Structure:

axelor-repos/
├── axelor-core-7.2.6/
│   ├── classes/    # .class files from JAR
│   └── sources/    # .java files from sources JAR
└── axelor-base-8.2.9/
    ├── classes/
    └── sources/

3. ASMExtractor.py

Python client for ASMAnalysisService with SQLite storage.

Database Schema:

-- Symbol index (FQN → URI → package)
CREATE TABLE symbol_index (
    fqn TEXT PRIMARY KEY,
    uri TEXT NOT NULL,
    package TEXT NOT NULL,
    line INTEGER                    -- Line number (methods only)
);

-- Nodes (classes and methods)
CREATE TABLE nodes (
    fqn TEXT PRIMARY KEY,
    type TEXT NOT NULL,             -- 'class', 'interface', 'enum', 'method'
    package TEXT NOT NULL,
    line INTEGER,
    visibility TEXT,                -- 'public', 'private', 'protected', 'package'
    has_override BOOLEAN,           -- TRUE if @Override annotation present
    is_transactional BOOLEAN        -- TRUE if @Transactional annotation present
);

-- Edges (relationships)
CREATE TABLE edges (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    from_fqn TEXT NOT NULL,
    edge_type TEXT NOT NULL,        -- 'call', 'inheritance', 'member_of'
    to_fqn TEXT NOT NULL,
    kind TEXT,                      -- 'invoke', 'extends', 'implements', 'argument', 'return'
    from_package TEXT NOT NULL,
    to_package TEXT NOT NULL,
    from_line INTEGER
);

Constructor:

ASMExtractor(db_path=".callgraph.db", service_url="http://localhost:8766", init=False)
  • init=True: Full reset - drops and recreates all tables (INIT mode)
  • init=False (default): Incremental mode - creates tables if they don't exist

Key Methods:

  • init_database(): Full reset - drops and recreates all tables (called automatically when init=True)
  • clean_package_data(package_name): Removes all data for a specific package (used automatically in incremental mode)
  • build_symbol_index(): Builds FQN → URI mapping with automatic cache invalidation
  • extract(): Extracts call graph from bytecode

Performance:

  • Symbol indexing: ~6 minutes for 39 packages (107k symbols, 9.4k classes)
  • Call graph extraction: ~40 seconds for 9,466 .class files (252 files/sec)
  • Total extraction (--init): ~6-7 minutes complete project
  • Incremental mode: Only re-extracts modified packages (70%+ speedup on subsequent runs)

Optimizations:

  • Batch SQL queries (IN clauses): 99.95% reduction in database queries
  • Before: ~650k queries → After: ~220 queries
  • Insertions by batches of 5000 rows

4. ASMAnalysisService (Java)

REST service for analyzing Java bytecode using ASM.

Technology:

  • ASM: Bytecode manipulation framework
  • Spark Java: Lightweight HTTP framework
  • Jackson: JSON serialization

Endpoints:

  • GET /health - Health check
  • POST /index - Extract symbols (classes AND methods) with nodeType and line
  • POST /analyze - Extract complete call graph with metadata
  • POST /shutdown - Gracefully shut down the service

Extracted Metadata:

  • Class modifiers (public, abstract, final, etc.)
  • Method modifiers and visibility
  • Annotations: @Override, @Transactional (Spring, Jakarta, javax)
  • Line numbers for methods
  • Inheritance relationships
  • Method calls with line numbers

Running the Service:

cd Extracteurs/ASMAnalysisService
./gradlew.bat run
# Service starts on port 8766
# Logs written to asm-service.log

MCP Tools (SQLite)

Use your configured SQLite MCP server with direct SQL queries.

Query Examples

Find methods using class X as argument:

SELECT DISTINCT e.to_fqn AS method_fqn, e.from_fqn AS argument_type
FROM edges e
WHERE e.edge_type = 'member_of' AND e.kind = 'argument'
  AND e.from_fqn = 'com.axelor.apps.openauction.db.Lot'
ORDER BY e.to_fqn;

Find all calls to a method:

SELECT e.from_fqn AS caller, e.to_fqn AS callee, e.from_line AS line, e.from_package
FROM edges e
WHERE e.edge_type = 'call' AND e.to_fqn LIKE '%setStatus%'
ORDER BY e.from_package;

Count symbols by package:

SELECT package, COUNT(*) as symbol_count,
       SUM(CASE WHEN type = 'class' THEN 1 ELSE 0 END) as class_count,
       SUM(CASE WHEN type = 'method' THEN 1 ELSE 0 END) as method_count
FROM nodes
GROUP BY package ORDER BY symbol_count DESC;

Find transactional methods:

SELECT fqn, package, line
FROM nodes
WHERE type = 'method' AND is_transactional = 1
ORDER BY package, line;

Find public methods with @Override:

SELECT fqn, package, line
FROM nodes
WHERE type = 'method' AND visibility = 'public' AND has_override = 1
ORDER BY package, line;

Comparison: JavaParser vs ASM

FeatureJavaParser (Old)ASM (New)
InputSource code (.java)Bytecode (.class)
Requires sourcesYesNo (works with JARs)
Analysis speedSlower (parsing)Faster (bytecode)
AccuracySource-levelBytecode-level (100%)
DependenciesManual downloadGradle auto-discovery
StorageChromaDB (~150 MB)SQLite (~50 MB)
Query speed~50ms~10ms

Benefits of ASM Approach

  1. No source code required: Works directly with JARs from Maven/Gradle cache
  2. Automatic dependency discovery: Gradle integration
  3. 100% accurate: Bytecode analysis reflects actual compilation
  4. Faster: No parsing overhead
  5. Smaller database: Relational storage vs vector embeddings
  6. Better caching: SHA256-based invalidation
  7. Simpler queries: SQL vs vector similarity search

Performance

Extraction Times (Real Project - 39 Packages)

Full Extraction (--init): ~6-7 minutes total

  • STEP 1 (Gradle discovery): ~10 seconds
  • STEP 2 (Symbol indexing): ~6 minutes for 107k symbols
  • STEP 3 (Call graph extraction): ~40 seconds for 9,466 .class files (252 files/sec)

Incremental Mode: Only re-extracts modified packages (70%+ speedup)

Database Stats

Real Project Example:

  • Size: ~292 MB
  • Symbols: 107,312 (9,466 classes + 97,846 methods)
  • Edges: 319,272 (calls, inheritance, member_of)
  • Packages: 39 Axelor packages
  • Entities: 2,089 (22% of classes)

Database Contents:

  • symbol_index: 107,312 rows (~60 MB)
  • nodes: 94,267 rows (~52 MB)
  • edges: 319,272 rows (~180 MB)

Query Performance

  • Simple queries (COUNT, SELECT WHERE): ~10ms
  • Complex joins (call chains): ~50-100ms
  • Full-text search (LIKE): ~20-30ms

Troubleshooting

ASMAnalysisService not starting

Solutions:

  1. Check Java: java -version (need Java 11+)
  2. Build service: cd Extracteurs/ASMAnalysisService && gradlew.bat build
  3. Start manually: gradlew.bat service:run

Cache not detected

Verification:

ls axelor-repos/axelor-core-7.2.6/classes/
# Should contain .class files

FQN resolution fails or stale data

Solution:

  • Full reset: python run_asm_extraction.py /path/to/project --init
  • This drops all tables and rebuilds from scratch

Kill stuck Java processes

# Windows
wmic process where "name='java.exe' and CommandLine like '%ASMAnalysisService%'" delete

Development

Project Structure

CallGraph/
├── run_asm_extraction.py            # CLI entry point
├── mcp_callgraph_server.py          # Legacy MCP server
├── mcp.json                         # MCP configuration
├── ASM_ARCHITECTURE.md              # Detailed architecture
├── Extracteurs/
│   ├── GradleDependencyManager.py   # Gradle integration
│   ├── ASMExtractor.py              # Python client
│   └── ASMAnalysisService/          # Java service
│       └── service/                 # Kotlin/Gradle implementation
└── axelor-repos/                    # Cached Axelor packages

File Naming Conventions

PascalCase for Python files (matches main class):

  • ASMExtractor.pyclass ASMExtractor
  • GradleDependencyManager.pyclass GradleDependencyManager

License

MIT

Resources