blog

codebase-memory-mcp: dựng knowledge graph cho codebase và những lưu ý

Một binary C index cả repo thành knowledge graph để agent hỏi bằng graph query thay vì grep từng file. Cách cài, ba tool dùng nhiều nhất, và hai lỗi im lặng làm graph sai mà vẫn báo indexed.

codebase-memory-mcpMCPClaude CodeKnowledge GraphAI Agent
So sánh hai cách agent đọc code - bên trái là các dòng file đọc tuần tự, bên phải là đồ thị node nối nhau theo chuỗi gọi hàm

Agent code đốt token chủ yếu vào việc đi tìm, không phải việc sửa. Mỗi câu hỏi "hàm này ai gọi" là vài lượt grep cộng vài lần đọc nguyên file. codebase-memory-mcp index cả repo thành knowledge graph rồi trả lời cùng câu hỏi đó bằng một truy vấn — nhưng chỉ đúng khi bạn kiểm soát được cái gì lọt vào index, và mặc định thì không.

codebase-memory-mcp là gì: một binary C, 15 MCP tool, không LLM bên trong

Nó là MCP server viết bằng C, phát hành MIT, parse code bằng 158 grammar tree-sitter nhúng sẵn trong binary. Kết quả đổ vào SQLite ở ~/.cache/codebase-memory-mcp/: node là Function, Class, File, Route, Resource…, cạnh là CALLS, IMPORTS, DEFINES, IMPLEMENTS, HTTP_CALLS

Ba điểm quyết định trải nghiệm:

  • Không có LLM bên trong. Tool chỉ dựng và truy vấn graph; agent bạn đang chat mới là lớp dịch câu hỏi thành query. Không API key, không model phải cấu hình.
  • Một binary tĩnh. Không Docker, không Node, không Python, không language server chạy nền. macOS arm64/amd64, Linux arm64/amd64, Windows amd64.
  • Trên tree-sitter còn một lớp type resolution mà repo gọi là Hybrid LSP, viết bằng C cho Python, TypeScript/JavaScript/JSX/TSX, PHP, C#, Go, C, C++, Java, Kotlin, Rust và Perl. Đây là lớp làm cho user.profile.display_name() nối được về đúng Profile.display_name khai báo ở module khác — thứ tree-sitter thuần không làm được.

Repo mở tháng 2/2026, tới nay 34k sao, bản mới nhất v0.9.0 ra ngày 8/7/2026 với 147k lượt tải asset. Cũng đang có 301 issue mở — phần sau nói kỹ.

Cài bằng một dòng curl, và danh sách file nó ghi vào máy

macOS và Linux:

bash
curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash

Ba cờ đáng biết: --ui lấy bản có giao diện đồ thị 3D, --skip-config chỉ cài binary và không đụng vào config agent, --dir=<path> đổi chỗ cài. Binary rơi vào ~/.local/bin/codebase-memory-mcp.

Trên Windows thì tải install.ps1 về, Unblock-File rồi chạy — script bị Mark-of-the-Web chặn nếu bỏ bước đó.

Phần cần đọc kỹ: installer tự dò agent đã cài rồi ghi vào config của chúng, tổng cộng 43 client surface. Với Claude Code trên máy tôi, nó chạm bốn chỗ:

json5
// ~/.claude.json
"codebase-memory-mcp": { "command": "/Users/musk/.local/bin/codebase-memory-mcp" }

// ~/.claude/settings.json — ba hook
"SessionStart":  "~/.claude/hooks/cbm-session-reminder"
"SubagentStart": "~/.claude/hooks/cbm-subagent-reminder"
"PreToolUse":    { "matcher": "Grep|Glob", "command": "~/.claude/hooks/cbm-code-discovery-gate" }

// ~/.claude/skills/codebase-memory/SKILL.md

Hook tên cbm-code-discovery-gate nghe như chặn tool, nhưng nó chỉ chèn thêm context và không bao giờ từ chối lượt gọi Grep/Glob — cái tên là di sản để tương thích ngược. Dù vậy, đây vẫn là một tool tự sửa file cấu hình agent của bạn ở scope user. Muốn tự tay khai thì --skip-config rồi thêm block mcpServers bằng tay.

Gỡ sạch: codebase-memory-mcp uninstall — nó xoá đúng những mục nó tạo, và chỉ xoá index sau khi hỏi.

Sau khi cài, khởi động lại agent rồi bảo "index project này". Kiểm tra bằng /mcp, phải thấy 15 tool.

3 tool trả lời được thứ grep không trả lời được

search_graph tìm theo cấu trúc: regex tên, lọc theo label, theo file, theo bậc của node. 3 chế độ độc lập — query cho BM25 tiếng tự nhiên có tách camelCase, name_pattern cho regex, semantic_query cho vector search.

trace_path là tool tôi gọi nhiều nhất. Nó BFS trên cạnh CALLS, depth 1–5, hướng vào hoặc ra. Chạy trên chính site này:

bash
trace_path(function_name="...pages.[[...slug]].getStaticProps", direction="outbound", depth=3)
text
hop 1  lib.mdx.getPageBySlug, getRelatedRecords, generateCollectionRss
hop 2  lib.mdx-files.getFileBySlug, getManyFilesBySlug, isCollectionFile
       lib.mdx-options.getMdxOptions, lib.mdx-parser.Parser
hop 3  lib.mdx-files.getFiles

Đúng pipeline MDX của repo, xuyên qua bốn file, không cần biết trước tên file nào. Tên hàm trùng ở nhiều nơi thì nó trả status: "ambiguous" kèm danh sách qualified name để chọn lại — hành vi đúng, thay vì đoán bừa một cái.

Số token cho chuỗi trên: response JSON khoảng 330 token. Đọc tay năm file trong chuỗi đó là 18.314 byte, khoảng 5.000 token. Tức khoảng 15 lần, cho một câu hỏi. Con số 120 lần trên README đo trên năm truy vấn cấu trúc gộp lại và là kịch bản có lợi nhất cho tool; 10–15 lần cho một câu hỏi thường mới là mức tôi thấy lặp lại.

query_graph chạy Cypher chỉ đọc. Dead code bằng một câu:

cypher
MATCH (f:Function)
WHERE NOT EXISTS { (f)<-[:CALLS]-() }
RETURN f.name, f.file_path

Mọi thứ ngoài subset (MERGE, CALL, list comprehension, tham số) trả lỗi unsupported … rõ ràng chứ không trả rỗng — chi tiết nhỏ nhưng cứu được kha khá thời gian debug.

Mọi tool đều gọi được từ terminal, tiện để thử trước khi nối vào agent:

bash
codebase-memory-mcp cli list_projects
codebase-memory-mcp cli search_graph '{"project":"my-project","name_pattern":".*Handler.*"}'

Từ v0.9.0, truyền JSON thô kiểu này in cảnh báo deprecated; dạng mới là flag hoặc --args-file.

Lưu ý 1: 76% graph của site này là bundle đã minify

Repo này commit thư mục out/ vào git vì nginx trỏ thẳng root vào đó — kiến trúc static export của site đổi deploy thành một lần git pull. Index xong, graph có 2.831 node. Đếm lại theo đường dẫn:

cypher
MATCH (n) WHERE n.file_path STARTS WITH 'out/' RETURN count(n)
-- 2164

2.164 trên 2.831 node là JavaScript đã minify. Câu truy vấn dead code phía trên trả về những cái tên như F trong out/_next/static/chunks/turbopack-*.js — vô nghĩa hoàn toàn. Bất kỳ repo nào commit build output, dist/, vendor/ hay file bundle đều dính y hệt.

Biểu đồ so sánh số node trước và sau khi thêm .cbmignore: 2.831 node trong đó 2.164 đến từ thư mục out, còn lại 655 node là source thật
Biểu đồ so sánh số node trước và sau khi thêm .cbmignore: 2.831 node trong đó 2.164 đến từ thư mục out, còn lại 655 node là source thật

Cách chặn là .cbmignore ở gốc repo, cú pháp giống .gitignore:

text
out/
public/search-index.json

Nhưng thêm file rồi index lại không đủ. Lần index lại đó báo excluded: ["out", ...] đúng như mong đợi, số node vẫn nguyên 2.831 và 2.164 node cũ vẫn nằm trong graph — index tăng dần chỉ thêm và cập nhật, không dọn thứ giờ mới bị loại. Phải xoá hẳn project rồi dựng lại:

bash
codebase-memory-mcp cli delete_project '{"project":"<tên project>"}'
codebase-memory-mcp cli index_repository '{"repo_path":"/đường/dẫn/tuyệt/đối"}'

Kết quả sau khi dọn: 2.831 → 655 node, 8.865 → 1.015 cạnh, file .db 10,9 MB → 3,0 MB. Cùng một repo, cùng một câu hỏi, khác nhau ở chỗ agent có phải lội qua bundle minify hay không.

Trước khi index repo đầu tiên:

  • Viết .cbmignore trước, liệt kê mọi thư mục build output có mặt trong git
  • Index xong thì đếm: MATCH (n) WHERE n.file_path STARTS WITH '<thư-mục>/' RETURN count(n)
  • Sửa .cbmignore sau khi đã index thì luôn delete_project rồi index lại

Lưu ý 2: .gitignore nuốt 1.166 file PHP mà vẫn báo "indexed"

Lớp lọc file xếp chồng: pattern cứng (.git, node_modules) → cả cây .gitignore.cbmignore. Lớp giữa là chỗ đau.

Tôi index một site WordPress: 494 file trong git, index trả về status: "indexed", 752 node, skipped_count: 0. Nhìn kỹ trường excluded thì thấy core. Repo đó gitignore thư mục core/ — nơi chứa 1.166 file PHP của WordPress. Toàn bộ lõi biến mất khỏi graph, và không có gì trong kết quả nói rằng graph đang thiếu; skipped_count vẫn là 0 vì với tool thì những file đó chưa từng tồn tại.

Kịch bản này phổ biến hơn vẻ ngoài: WordPress gitignore core, nhiều dự án PHP gitignore vendor/, dự án Go gitignore code sinh tự động. Agent hỏi "hàm wp_insert_post định nghĩa ở đâu" sẽ nhận về rỗng, rồi kết luận sai rằng hàm đó không tồn tại.

Cộng đồng gặp cùng lớp lỗi ở dạng nặng hơn: issue #333 báo status: "indexed" nhưng chỉ ra khoảng 500 node cho một codebase Rust 72k dòng. v0.9.0 có thêm biến CBM_DUMP_VERIFY_MIN_RATIO để trả status: "degraded" khi số node ghi xuống SQLite tụt dưới một nửa số node đã dựng, nhưng nó chỉ bắt lỗi ở khâu ghi, không bắt lỗi ở khâu chọn file.

Sau mỗi lần index, kiểm ba thứ:

  • Đọc trường excluded trong kết quả, không chỉ đọc status
  • Đối chiếu số node với quy mô repo — vài trăm node cho vài chục nghìn dòng là dấu hiệu hỏng
  • Thư mục source bị gitignore thì phủ định lại trong .cbmignore (!core/)

6 process chạy nền, 186 MB cache và 301 issue đang mở

Mỗi phiên agent mở một tiến trình stdio riêng, và chúng không tự chết. Trên máy tôi sau vài ngày:

text
5 × codebase-memory-mcp (stdio)   ~9–10 MB mỗi cái
1 × codebase-memory-mcp --ui      ~80 MB

Bản stdio nhẹ thật, nhưng số lượng chỉ có tăng. Bản --ui tốn gấp chín lần một tiến trình nền để đổi lấy đồ thị 3D ở localhost:9749 — tôi bật khi cần nhìn tổng thể một repo lạ, còn lại thì tắt. Kiểm nhanh bằng pgrep -fl codebase-memory.

Dung lượng đĩa cũng không nhỏ. Ba project trong cache của tôi ăn 186 MB, trong đó một repo Laravel 28.395 node chiếm 123 MB — khoảng 4,3 KB mỗi node, do embedding 768 chiều và index full-text nằm cùng file. Muốn đổi chỗ thì đặt CBM_CACHE_DIR.

Danh sách issue mở là thứ nên đọc trước khi đưa tool vào máy làm việc chính. Những cụm lặp lại nhiều nhất:

  • Rò bộ nhớ dài hạn#581: tiến trình phình lên hơn 50 GB virtual memory sau vài giờ tới vài ngày trên Windows
  • Index repo lớn không kết thúc hoặc bị kill: #524, #1070
  • PHP là ngôn ngữ nhiều lỗi nhất#1072 livelock và RAM không giới hạn, #606 cạnh CALLS dương tính giả khi gọi method của framework
  • trace_path trả rỗng dù cạnh CALLS có thật: #480
  • Windows là nền tảng yếu nhất, đủ nhiều lỗi để có hẳn một issue gom nhóm: #394

Đây là repo năm tháng tuổi đi rất nhanh — 36 release, phần lớn issue được trả lời. Nhưng 301 issue mở nghĩa là bạn sẽ gặp ít nhất một trong số đó, và nên biết trước đó là lỗi của tool chứ không phải của mình.

Điểm cộng thật sự nằm ở khâu phát hành: mỗi binary có checksum SHA-256, chữ ký cosign, provenance SLSA Level 3 và kết quả quét VirusTotal 0/72 công bố kèm release. Với một tool tự ghi vào config agent, mức minh bạch đó là thứ tối thiểu phải có, và ở đây nó có.

Cấu hình tôi giữ lại: auto_index bật, auto_watch tuỳ số repo

bash
codebase-memory-mcp config set auto_index true           # index project mới khi phiên bắt đầu
codebase-memory-mcp config set auto_index_limit 50000    # trần số file cho auto index
codebase-memory-mcp config set auto_watch true           # watcher theo dõi thay đổi qua git

auto_index bật thì project mới tự vào graph ngay lượt kết nối đầu, project cũ được đăng ký với watcher để cập nhật dần. Index tăng dần khi không có gì đổi mất 0,21 giây — rẻ đến mức không cần nghĩ.

Đổi lại, auto_watch bật là mỗi project đụng tới đều thành một mục thường trực. Nhảy qua lại giữa nhiều repo trong ngày thì tắt nó đi (auto_watch false) và index tay từng repo thật sự cần.

Repo có đuôi file lạ thì khai trong .codebase-memory.json ở gốc — Laravel là ví dụ rõ nhất:

json
{ "extra_extensions": { ".blade.php": "php" } }

Đội nhiều người thì bật persistence: true lúc index: tool ghi .codebase-memory/graph.db.zst (nén zstd, tỷ lệ 8–13:1) để commit vào repo, người mới clone về chỉ index phần chênh lệch thay vì dựng lại từ đầu.

Đánh đổi khi để agent đọc graph thay vì đọc file

Tool này đáng cài nếu bạn làm trên repo đủ lớn để việc tìm code tốn nhiều token hơn việc sửa code, và bạn chấp nhận bỏ mười lăm phút đầu để dựng .cbmignore cho tử tế. Cài xong quên đấy là cách nhanh nhất để agent đọc phải một graph nửa vời rồi trả lời chắc nịch một câu sai.

Chỗ tôi vẫn không tin tưởng: câu trả lời phủ định. Graph nói "không ai gọi hàm này" chỉ có nghĩa là trong phần đã index không ai gọi — và như hai mục trên cho thấy, phần chưa index có thể lớn hơn phần đã index. Với câu hỏi phủ định, tôi vẫn grep lại một lần.

Việc chưa làm: chưa thử ingest_traces để đối chiếu cạnh HTTP_CALLS bằng trace runtime, và chưa đo bản --ui trên repo trên một triệu node — chỗ #402 báo UI sập khi dựng đồ thị 3D.

Bài viết liên quan

tất cả bài viết