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.
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
- Parse — Python's stdlib
astwalks 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). - 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 —astowns identity and structure, Pyright only annotates it. - 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.
- Materialize closures —
calls,imports, andinheritsare 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 dedicatedurn:code:graph:inferredgraph. "Every caller of X, however deep" becomes a direct lookup, not a graph traversal. - 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.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。