首页
文章
归档 时间轴 分类 标签
小工具
工具 待办 图鉴

codebase-memory-mcp:用纯 C 把代码库变成知识图谱

简介

AI 编码 Agent(Claude Code、Cursor、Copilot 等)面对大型代码库时,通常的工作模式是”搜 → 读 → 搜 → 读”的循环。Agent 发出一条 grep "ProcessOrder",得到 20 个文件匹配,然后逐个 Read,再 grep 下一个关键词。一个中等规模的代码库探索任务,动辄消耗数十万 token,其中大部分是对同一文件上下文窗口的重复加载。

codebase-memory-mcp 的思路是:与其让 LLM 一遍遍读文件,不如提前把代码结构建成一张图,Agent 只需要问图就够了。一次 trace_path(function_name="ProcessOrder", direction="inbound") 返回的是完整调用链,而不是 20 个文件路径让 LLM 自己拼。

这个项目的核心数据:5 次结构化查询消耗约 3,400 token,对比逐文件 grep 的 412,000 token,减少 99.2%。

项目概览

属性详情
仓库DeusData/codebase-memory-mcp
Stars7.2k(截至 2026-06-19)
许可证MIT
语言纯 C(zero dependencies,vendored tree-sitter)
最新版本v0.8.1(2026-06-12)
核心架构多 pass pipeline + SQLite 知识图谱 + MCP JSON-RPC
支持语言158 种(tree-sitter 语法内置)
Hybrid LSP11 种(TypeScript、Python、Go、Rust、Java 等)
代码规模src/ 下 12 个子目录,pipeline/ 下约 30 个 pass 文件

技术栈

选型原因
语法解析tree-sitter(158 种语法内置)增量解析、容错、跨语言统一 API
图存储SQLite(单文件)嵌入式、零部署、WAL 模式并发读
语义解析自研 Hybrid LSP(C 实现)不依赖外部 LSP 进程,跨文件类型推断
向量搜索内置 nomic-embed-code 模型无 API 依赖,768 维 int8,本地推理
协议MCP(JSON-RPC 2.0 over stdin/stdout)与 Claude Code、Cursor 等 11 种 Agent 对接
图可视化内嵌 HTTP 服务器 + 3D UIlocalhost:9749,可选 UI 二进制

项目结构

src/
├── main.c              # 入口:信号处理、模式分发、看门狗
├── mcp/                # MCP 协议层(JSON-RPC 解析、工具注册)
│   └── mcp.c           # 188KB,14 个 MCP 工具的实现
├── pipeline/           # 索引流水线(核心)
│   ├── pipeline.c      # 编排器:管理所有 pass 的执行顺序
│   ├── pass_parallel.c # 并行 AST 解析(113KB,最大的 pass)
│   ├── pass_lsp_cross.c # Hybrid LSP 跨文件语义解析
│   ├── pass_calls.c    # 构建 CALLS 边
│   ├── pass_definitions.c # 构建 DEFINES 边
│   ├── pass_route_nodes.c # REST/gRPC 路由提取
│   ├── pass_infrascan.c # K8s/Docker 基础设施扫描
│   ├── pass_cross_repo.c # 跨仓库关联
│   ├── pass_semantic.c # 向量嵌入(nomic-embed-code)
│   └── ...             # 共 20+ 个 pass
├── store/              # SQLite 图存储层
├── cli/                # CLI 模式(直接运行单个工具)
├── watcher/            # 文件变更监听(git-based)
├── semantic/           # 语义搜索(11 信号综合评分)
├── simhash/            # MinHash 近似重复检测
├── cypher/             # Cypher 查询解析器
├── graph_buffer/       # 图缓冲区(内存压缩)
├── ui/                 # 内嵌 HTTP 服务器 + 3D UI
├── foundation/         # 基础库(日志、内存、平台抽象)
└── discover/           # Agent 配置自动检测

这个结构揭示了一个重要设计:pipeline 目录下每个 pass_*.c 都是流水线的一个阶段pipeline.c 负责编排它们的执行顺序和依赖关系。这是一种典型的编译器式架构。

核心架构

PlantUML Diagram

整个系统分三层:MCP 协议层(对接 Agent)、Pipeline 层(构建知识图谱)、存储/查询层(SQLite 图 + 多种查询接口)。Pipeline 的每个 pass 是一个独立的转换阶段,前一阶段的输出是后一阶段的输入。

这里有一个值得注意的设计决策:为什么每个 pass 是独立的 .c 文件,而不是在单次遍历中完成所有工作? 答案是依赖关系——构建 CALLS 边需要先有所有函数的定义(DEFINES),而 Hybrid LSP 类型推断需要跨文件的全局视图。拆分为独立 pass 允许 pipeline.c 按依赖顺序编排,同时让每个 pass 可以并行处理(pass_parallel.c 就是干这个的)。

源码导读

1. 入口点 main.c — 模式分发与生命周期管理

文件:src/main.c(20KB)

main.c 的核心逻辑可以用一句话概括:解析命令行参数,决定运行模式,然后启动对应的主循环

/* Modes:
 *   (default)       Run as MCP server on stdin/stdout (JSON-RPC 2.0)
 *   cli <tool> <json>  Run a single tool call and print result
 *   --version       Print version and exit
 *   --help          Print usage and exit
 *   --ui=true/false Enable/disable HTTP UI server (persisted)
 *   --port=N        Set HTTP UI port (persisted, default 9749)
 */

这段注释说明了五种运行模式,其中默认模式(MCP server)是最核心的。main.c 在这里做了一个有意思的选择:信号处理函数直接关闭 stdin

static void request_shutdown(void) {
    if (atomic_exchange(&g_shutdown, 1)) {
        return; /* already shutting down */
    }
    /* Cancel any in-progress pipeline */
    if (g_server) {
        cbm_pipeline_t *p = cbm_mcp_server_active_pipeline(g_server);
        if (p) cbm_pipeline_cancel(p);
    }
    cbm_pipeline_unlock();
    if (g_watcher) cbm_watcher_stop(g_watcher);
    if (g_http_server) cbm_http_server_stop(g_http_server);
    /* Close stdin to unblock getline in the MCP server loop */
    (void)fclose(stdin);
}

为什么要 fclose(stdin) 而不是直接 exit()?因为 MCP server 的主循环是阻塞式 getline(),直接 exit 会跳过资源清理。关闭 stdin 让 getline 返回 EOF,主循环自然退出。这是一个在 C 程序里比较优雅的异步关闭模式。

2. Pipeline — 编译器式多阶段流水线

文件:src/pipeline/pipeline.c(42KB)+ 30 个 pass_*.c 文件

Pipeline 是整个系统最复杂的部分。pipeline.c 是编排器,负责按正确顺序执行所有 pass,每个 pass 完成一个具体的图构建任务。

以 pass 执行顺序为例,简化后的流程:

1. pass_parallel.c      → 并行解析所有文件的 AST,提取函数/类定义
2. pass_definitions.c   → 将定义写入图的节点(Function, Class, Module...)
3. pass_pkgmap.c        → 扫描 package.json/go.mod/Cargo.toml,建立包映射
4. pass_lsp_cross.c     → Hybrid LSP 跨文件类型推断,解析 import 目标
5. pass_calls.c         → 基于类型推断结果,构建 CALLS 边
6. pass_route_nodes.c   → 提取 REST/gRPC 路由节点
7. pass_infrascan.c     → 扫描 K8s/Docker 配置,建立 Resource 节点
8. pass_semantic.c      → 计算向量嵌入,写入 SEMANTICALLY_RELATED 边
9. pass_cross_repo.c    → 如果有多个仓库,建立 CROSS_* 跨仓库边

注意 pass_lsp_cross.c 和 pass_calls.c 的依赖关系——CALLS 边的构建必须等 LSP 类型推断完成,因为 foo.bar() 到底调用哪个函数,需要知道 foo 的类型。这就是为什么拆分为独立 pass 而不是单次遍历:不同阶段有严格的先后依赖。

pass_parallel.c 是 113KB 的大文件,它做的事情是并行化 AST 解析。具体实现是启动一个 worker pool(worker_pool.c),把文件分块分发给多个线程,每个线程用 tree-sitter 解析自己负责的文件块,然后把结果合并到共享的图缓冲区。

3. Hybrid LSP — 跨文件类型推断的实现

文件:src/pipeline/pass_lsp_cross.c(24KB)+ pass_lsp_cross.h

“Hybrid LSP” 是这个项目技术含量较高的部分。标准做法是让 Agent 启动一个外部 LSP 进程(如 tsserver、pyright),通过 LSP 协议查询类型信息。codebase-memory-mcp 选择用纯 C 重新实现了这些语言的核心类型推断逻辑:

/* Hybrid LSP semantic type resolution for:
 * Python, TypeScript/JavaScript/JSX/TSX, PHP, C#, Go,
 * C, C++, Java, Kotlin, and Rust
 *
 * Structurally inspired by and compatible with:
 * tsserver, pyright, gopls, Roslyn, Eclipse JDT, rust-analyzer
 */

这个选择的代价是巨大的实现工作量(11 种语言的类型系统各写一遍),但收益也很明显:

维度外部 LSPHybrid LSP(内置)
部署需要安装 Node.js、Python 等运行时单二进制,零依赖
启动每个语言启动独立进程,几秒到几十秒直接在进程内完成
跨语言每个 LSP 只懂自己的语言一次遍历同时处理所有语言
维护跟随上游 LSP 版本需要自己跟进语言规范变更

从 README 的描述来看,它实现的不只是简单的符号查找,而是包括:参数绑定、返回类型推断、泛型替换、JSX 组件派发、JSDoc 推断(纯 JS 文件)、PHP 的 namespace + trait + 晚期静态绑定、C# 的文件范围命名空间 + record + LINQ、Java 的类层级 + 重载、Kotlin 的扩展函数、Rust 的 UFCS 等。

这是一个大胆的工程决策——把原本需要多个 LSP 进程的工作,用 C 在一个二进制里实现。性能收益是确定的(进程间通信的开销被完全消除),但维护成本也是真实的。

关键设计决策

决策原因权衡
纯 C 实现,零依赖单二进制分发、启动快、内存可控开发效率低,维护成本高(5604 个测试用例说明一切)
tree-sitter 而非语言专有解析器统一 158 种语言的 AST APItree-sitter 不提供语义信息,需要自建 Hybrid LSP
SQLite 存储图嵌入式、零部署、WAL 并发读不适合超大规模并发查询(但 Agent 场景本来就不是)
RAM-first 索引磁盘 I/O 是瓶颈,内存带宽更高大仓库索引时内存占用高,但完成后立即释放
不内置 LLM避免 API key 依赖和额外成本Agent 必须是 MCP 兼容的,无法独立使用
多 pass pipeline清晰的依赖管理、可并行化文件多、代码量大(pipeline/ 下 ~700KB C 代码)

与同类方案对比

维度codebase-memory-mcp逐文件 grep + Readrepo-map(Aider)
索引时间(Linux 内核)3 分钟无需索引,但查询慢无公开基准
查询延迟< 1ms(图遍历)秒级(多次 grep + LLM 处理)取决于 LLM 上下文
Token 消耗~3,400(5 次查询)~412,000(同一任务)较高(需传入完整 map)
部署单二进制,零依赖内置于所有 Agent需要 Python 环境
语言支持158 种(tree-sitter)取决于 grep主要支持主流语言
语义理解Hybrid LSP(11 种语言)

codebase-memory-mcp 和 Aider 的 repo-map 解决的是同一类问题(减少 Agent 的 token 消耗),但路径完全不同。repo-map 是把代码结构压缩成文本传给 LLM,codebase-memory-mcp 是把代码结构建成图让 Agent 查询。前者依赖 LLM 的理解能力,后者依赖图数据库的查询能力。

适用场景与局限

收益取决于代码库规模——索引有一次性成本,规模越大越划算:

  • 大型代码库(> 10 万行)、微服务跨仓库分析:索引一次后查询的 token 节约显著,CROSS_* 边天然支持跨仓库关联。
  • 小型脚本项目(< 1000 行):索引成本超过收益,直接 grep 更快。
  • 静态分析:图结构天然支持调用链、影响面分析。

语言精度差异:Hybrid LSP 覆盖 11 种主流语言,提供语义级精度;其他语言(含 Ruby、Perl 等动态语言)只有 tree-sitter 的 AST 级精度,缺少 LSP 语义层。

参考资料

100%

Lv.1 亲密度 0