code-index

code-index

Provides structural, queryable understanding of a Python codebase via MCP tools, enabling direct lookups for callers, dependencies, and class hierarchies without repeated grep/read cycles.

Category
Visit Server

README

code-index

Structural, queryable understanding of a Python codebase for a coding agent — built as an AST-derived RDF graph, stored in an embedded triple store, and exposed as MCP tools.

Instead of an agent re-discovering "who calls this function" or "what does this class inherit from" via repeated grep/read cycles, code-index parses the codebase once into a graph of facts and answers those questions as direct lookups, in milliseconds.

Why

Grep and file reads are the default way an LLM coding agent explores a repository, but they're the wrong tool for structural questions:

  • "What calls this function, anywhere in the repo, directly or transitively?" — grep finds text matches, not resolved call edges.
  • "What does this class inherit from, or what inherits from it?" — requires actually resolving imports across files.
  • "Give me a cheap overview of this folder before I decide what to read in full." — grep/read either gives you everything or nothing.

code-index answers these by parsing structure once and storing it as facts, so the agent can query instead of re-deriving.

Concept

  1. Parse — Python's stdlib ast walks each file and extracts structural facts: modules, classes, functions, signatures, line ranges, docstrings, decorators, variable reads/writes, and unresolved call/import/inherit targets (by name only, not yet a link to the actual defining symbol).
  2. Resolve — a real Pyright language server runs as a managed subprocess for the duration of a build. It supplies return-type inference (hover) and cross-file symbol resolution (definition), which get attached onto the AST-built nodes — ast owns identity and structure, Pyright only annotates it.
  3. Store — every fact becomes an RDF triple (subject-predicate-object) in an embedded pyoxigraph store, one named graph per source file. This makes incremental re-indexing a drop-and-replace of a single file's graph, and gives the whole thing a real SPARQL 1.1 query engine for free.
  4. Materialize closures — calls, imports, and inherits are transitive relationships. Rather than walking property paths at query time, the full transitive closure of each is precomputed at index time and written into a dedicated urn:code:graph:inferred graph. "Every caller of X, however deep" becomes a direct lookup, not a graph traversal.
  5. Serve — an MCP server exposes six tools over the store: a token-cheap structural browser (get_context), a full-detail single-node lookup (get_details), three curated graph queries (get_callers, get_dependencies, get_class_hierarchy), and a raw SPARQL escape hatch (query_sparql) for anything the curated tools don't cover.

Everything downstream of parsing is just facts and queries over those facts — no LLM involved in indexing itself, so results are exact, not guessed.

Example: what actually gets stored

For a class like this:

class FeatureScaler:
    """Scales numeric features into [0, 1]."""

    def transform(self, values: list[float]) -> list[float]:
        """Scale values into [0, 1]."""
        return [self._normalize(v) for v in values]

parsing produces triples roughly equivalent to:

urn:code:src/scaler.py::FeatureScaler            a               code:Class
urn:code:src/scaler.py::FeatureScaler            code:defines    urn:code:src/scaler.py::FeatureScaler.transform
urn:code:src/scaler.py::FeatureScaler.transform  a               code:Function
urn:code:src/scaler.py::FeatureScaler.transform  code:signature  "transform(self, values: list[float]) -> list[float]"
urn:code:src/scaler.py::FeatureScaler.transform  code:description "Scale values into [0, 1]."
urn:code:src/scaler.py::FeatureScaler.transform  code:calls      urn:code:src/scaler.py::FeatureScaler._normalize
urn:code:src/scaler.py::FeatureScaler.transform  code:startLine  "5"

get_context("src/scaler.py") returns the cheap structural summary (names, signatures, line ranges — no docstrings). get_details(node_id) returns everything for one specific node, including its docstring. get_callers and get_class_hierarchy are direct lookups against the precomputed transitive closure, not live traversals.

Install

Requires Python 3.13+, uv, and Node.js (used only to vendor pyright-langserver as an internal subprocess — never a user-facing server).

git clone https://github.com/RaviIITk/code-index.git
cd code-index
uv sync
npm install       # vendors pyright-langserver into node_modules/

Usage

CLI

# Build (or incrementally rebuild) the index for a repo
uv run code-index build /path/to/repo

# Check what's currently indexed
uv run code-index status /path/to/repo

# Run a raw SPARQL query against the index
uv run code-index query "SELECT ?fn WHERE { ?fn a <http://example.org/code-ontology#Function> }" /path/to/repo

The index itself is stored outside the repo, in ~/.cache/code-index/<repo-slug>-<hash>/, keyed by the repo's canonical path — nothing is written into the working tree.

As a Python library

Everything the CLI and MCP server do is just calls into the library — useful for scripting or embedding in something else:

from pathlib import Path

from code_index.cache.location import store_path
from code_index.incremental.build import run_incremental_build
from code_index.mcp_server import tools
from code_index.store.triple_store import TripleStore

repo = Path("/path/to/repo")
store = TripleStore(store_path(repo))

# Build (or incrementally refresh) the index
report = run_incremental_build(store, repo, pyright_bin="pyright-langserver")
print(report.added, report.changed, report.deleted, report.parse_failures)

# Query it with the same functions the MCP tools delegate to
print(tools.get_context(store, "src/scaler.py", depth=1))

fn_id = "urn:code:src/scaler.py::FeatureScaler.transform"
print(tools.get_details(store, fn_id))
print(tools.get_callers(store, fn_id))
print(tools.get_dependencies(store, "src/scaler.py"))
print(tools.get_class_hierarchy(store, "urn:code:src/scaler.py::FeatureScaler"))

# Or drop to raw SPARQL 1.1
print(tools.query_sparql(store, "SELECT ?fn WHERE { ?fn a <http://example.org/code-ontology#Function> }"))

node ids are the same urn:code:<file_path>::<qualified.name> IRI strings returned in every get_context/get_details result's "id" field, so you can chain a broad query into a specific one without hand-building IRIs.

As an MCP server

Register it with an MCP-speaking agent host by adding to that host's MCP config (e.g. .mcp.json):

{
  "mcpServers": {
    "code-index": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/code-index", "code-index-mcp", "/path/to/repo"]
    }
  }
}

This starts a resident server (one per repo, guarded by an exclusive lock) exposing six tools:

Tool Signature Purpose
get_context (path: str, depth: int = 1) -> dict Token-cheap structural summary rooted at a file or folder; increase depth to drill from folder → file → function/class.
get_details (node: str) -> dict Full structural facts for one node, plus its docstring if the source has one.
get_callers (fn: str) -> list[str] Every function that calls fn, directly or transitively.
get_dependencies (file: str) -> list[str] Every file that file transitively imports.
get_class_hierarchy (cls: str) -> dict Transitive ancestors and descendants of a class.
query_sparql (query: str) -> list[dict] Raw SPARQL 1.1 escape hatch; the tool description embeds an ontology cheat-sheet so the agent has schema context without an extra call.

A typical agent workflow: call get_context on the repo root to see the folder tree, drill into a file of interest, grab a function/class id from that summary, then call get_details or get_callers with that id for the full picture.

Note: the MCP server only ever serves whatever is already in the store when it starts — run code-index build (or wire a session-start hook to do so) before pointing an agent at a repo whose index may be stale.

Development

uv run pytest    # 44 tests
uv run ruff check .
uv run ruff format .

Scope

v1 targets small-to-medium Python repos (hundreds of files). Deliberately out of scope for now: other languages, LLM-generated summaries/purpose fields, graph-centrality ranking, and transparent multi-session server sharing (each repo currently gets one resident server; a second concurrent session refuses to start rather than silently sharing state).

License

Dual-licensed under either of

at your option.

Recommended Servers

playwright-mcp

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.

Official
Featured
TypeScript
Magic Component Platform (MCP)

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.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

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.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

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.

Official
Featured
TypeScript
Kagi MCP Server

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.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

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.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured