I Replaced grep-Based Code Review with a Knowledge Graph + MCP. Here Are 3 Bugs Vector Search Missed.
For about a year, my AI code review setup looked like this: AI gets a PR, AI greps for related code, AI reads way too many files, AI says "looks fine." It mostly worked. Until the bugs that didn't show up in grep started shipping. The problem wasn't the model. It was the retrieval. Vector search and keyword grep are great at finding files that mention auth.py . They're terrible at finding files that depend on auth.py through three import hops, an event bus, and a decorator. That's where the bugs live. I rewired the retrieval layer with a code knowledge graph plugged in through MCP. Three bugs surfaced in the first week that vector search had been quietly missing. Here's what changed and the bugs themselves. Why grep + vector search missed these Vector search retrieves by semantic similarity. "Find code about authentication" finds auth.py , login.py , password_validator.py . Useful. Knowledge graphs retrieve by structural relationship. "What depends on auth.py ?" returns the call graph -- including event_handlers/login_event.py , which never mentions auth in its variable names but listens to a login event whose payload changes when auth.py changes. Both are valid. They answer different questions. The bugs that ship to production tend to live in the second question. The setup: code KG as an MCP server The Model Context Protocol (MCP), released by Anthropic in late 2024, lets you expose tools to a model in a standard way. By 2026 it's supported by Claude Code, Cursor, Windsurf, Zed, VS Code, and (as of GA in May 2025) the official MCP Registry hosts hundreds of servers. I used code-review-graph, an open-source tool that builds a property graph of your codebase and exposes it as an MCP server. The setup is a three-line ritual: pip install code-review-graph code-review-graph build ./my-project code-review-graph install # auto-detects Claude Code / Cursor / Windsurf The graph contains nodes for files, classes, functions, and tests, with edges for imports, calls, inheritance, decorates, listens-to, and tested-by. Once it's wired in, the AI can call MCP tools like: | Tool | What it answers | |---|---| blast_radius(file) | Every file that depends on this one (N hops) | flow_trace(func) | Where a function's output flows | semantic_search(query) | Hybrid: vector + graph proximity | community_detect() | Tightly-coupled modules | risk_score(diff) | Numerical risk of a change | dead_code() | Unreachable from any entry point | Anthropic's MCP rollout in 2025 also brought OAuth, prompts, and resource subscriptions, so the graph can push updates when files change instead of being re-queried each turn. That detail matters at scale -- code KGs are not cheap to walk, and stale snapshots are how teams ship bugs. The token math (the cheap reason to bother) Before the graph, my AI reviewer was getting context like this: PR diff: auth.py + 1 file Reviewer context: grep for "auth" -> 50 related files Tokens: ~150,000 After the graph, it gets this: PR diff: auth.py + 1 file Reviewer context: blast_radius("auth.py", hops=2) -> 7 files Tokens: ~18,000 The first time I ran it, the AI answered the review question in two seconds with a 7-file context. I had spent 30 minutes the day before grepping the same answer by hand. That moment of "what was I doing with my career" is, I think, the actual product of harness engineering. But cheaper context isn't the interesting part. The interesting part is which bugs the graph surfaced that grep + vector had missed. The 3 bugs Bug 1: the silent contract change (event handler) The diff was small. auth.py added a device_id field to its login event payload. Vector search retrieved login.py , auth_test.py , password_validator.py -- the obvious neighbors. The reviewer approved. The graph retrieved one extra file: event_handlers/audit_log.py . It listens to login_event and serializes the payload to a fixed schema in S3. Adding a new field broke the schema validator on every login. Production caught fire 90 minutes after merge. Why grep missed it: audit_log.py doesn't import auth.py . It listens to an event bus. There's no string match on "auth" in the file. What the graph saw: auth.py --emits--> login_event --consumed-by--> audit_log.py . Three hops, zero string matches, but a clean structural path. A code review pass that doesn't follow event subscriptions is a code review pass that doesn't review event-driven systems. Bug 2: the decorator surprise (transitive change) A teammate refactored @with_retry to add a backoff parameter. The default value was the same, so existing callers were "unaffected." Reviewer approved on the strength of the unit tests. Vector search retrieved files that explicitly imported the decorator. About a dozen. The graph retrieved 31 files. The 19 the graph added were files that applied @with_retry to functions that, three calls deep, ended up calling a function whose retry behavior had subtly changed under load. One of those callers was a payments webhook handler. Under retry, it now waited an extra 800ms before raising. That 800ms put it past the webhook timeout. We started losing about 0.4% of webhook deliveries silently. Why grep missed it: a decorator's effect propagates to every callsite of every decorated function. That's structural, not lexical. What the graph saw: with_retry --decorates--> {19 functions} --called-by--> {31 files} . The caller of a decorated function inherits the decorator's behavior, even if it never mentions the decorator's name. Bug 3: the orphan test (false confidence) A migration changed the way one ID was hashed. The PR included a test that asserted the new hash. CI was green. The graph showed something the test runner didn't: that test was in a file that no longer ran in CI because it had been moved out of the tests/ directory three weeks earlier and nobody had updated the path glob in the CI config. The test passed because the test file was never executed. The PR landed with a broken hash that corrupted the migration's first 8,000 rows. The graph had dead_code() for unreachable functions and an inverse query for unreachable test files. I'd never asked it. After this PR, I added "run dead_code() on test files" to the postflight check. Why grep missed it: grep doesn't know what CI runs. A test file's existence and a test file's execution are different facts. What the graph saw: test_user_id.py had no incoming edge from any CI config and no tested-by edges from production code. It was a green file in a green repo that didn't actually test anything. What the graph doesn't replace I don't want to oversell this. Three things the graph is bad at, and you should keep using vector search or grep for: - Natural-language queries. "Find code about authentication" is still better answered by a sentence-embedding model. The graph wants a node name. - New code with no edges yet. If the function was added in the diff, the graph knows it exists but has no incoming edges. Grep is fine here. - Cross-repo retrieval. Most code KG implementations are per-repo. If your auth lives in another service, you need a cross-repo strategy (or, increasingly, a cross-repo MCP server -- this is where Sourcegraph and Context.ai are converging in 2026). The right setup is hybrid: vector search for "find related concepts," graph for "find dependents," grep for "find this exact string." MCP makes that hybrid trivial because the model picks the tool per question. The 2026 landscape, briefly Three things have changed since I started doing this in late 2024: - GitHub Copilot Chat added repo-wide knowledge graph context in March 2026, walking the call graph for @workspace queries instead of relying purely on file embeddings. - Sourcegraph shipped an MCP server that exposes its long-standing code graph to any MCP-compatible IDE. They had this graph in 2017; the MCP wrapper is what makes it model-accessible. - Cursor integrated repomap for project-wide structural context. Not a graph, technically, but the same idea: structural retrieval beats lexical retrieval for cross-file changes. The pattern is converging. By the end of 2026, "AI code review" without a structural retrieval layer is going to look the way "AI code review without a vector store" looked in 2023. Quaint. What to wire up if you're starting If you don't already have something like this, the pragmatic order is: - Build the graph for one repo. code-review-graph or equivalent. - Expose it as an MCP server so your AI tool of choice can call it. - Add blast_radius to the postflight check for every PR. Just print the list of 2-hop dependents next to the PR. Even without the AI doing anything, the human reviewer reads better. - Keep your vector search and grep. Don't rip them out. Add the graph alongside. - Wait for the bugs to surface. They will. I had the graph for two weeks before the first bug it caught -- the audit log schema break. I don't think I would have shipped that bug without it. I do know I shipped six versions of it across my career before I had this tool. Closing For the last decade, code search has meant "embed the file and find similar embeddings." That's a fine answer to half the question. The other half -- "what does this change break?" -- is structural, and embeddings don't see it. The graph sees it. MCP makes the graph addressable. Together they collapse most cross-file retrieval into a single, cheap query. The bugs that used to live in those gaps don't, anymore. Want the full rationale and more graph patterns? I cover graph schema design, GraphRAG vs vector RAG, and code-as-graph patterns in Knowledge Graph Practical Guide: From RAG Limits to Graph-Native AI. References - Model Context Protocol -- Anthropic, 2024 - code-review-graph -- Open-source code KG with MCP server - GraphRAG: From Local to Global -- Microsoft Research, 2024 - Sourcegraph MCP integration -- 2026 MCP server announcement - GitHub Copilot Workspace Context -- Repo-graph integration, March 2026 Top comments (0)
Comments
No comments yet. Start the discussion.