I Replaced grep-Based Code Review with a Knowledge Graph + MCP. Here Are 3 Bugs Vector Search Missed. A developer replaced grep-based code review with a knowledge graph integrated via the Model Context Protocol (MCP), uncovering three bugs that vector search had missed. The graph retrieves code by structural relationships, such as dependencies and call flows, rather than semantic similarity, enabling the AI reviewer to identify issues like a schema-breaking change in an audit log handler. The setup reduced context tokens from 150,000 to 18,000 for a typical PR review. 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. 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 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 https://github.com/codelayers/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. Before the graph, my AI reviewer was getting context like this: php PR diff: auth.py + 1 file Reviewer context: grep for "auth" - 50 related files Tokens: ~150,000 After the graph, it gets this: php 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 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. 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. 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. I don't want to oversell this. Three things the graph is bad at, and you should keep using vector search or grep for: 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. Three things have changed since I started doing this in late 2024: @workspace queries instead of relying purely on file embeddings. 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. If you don't already have something like this, the pragmatic order is: code-review-graph or equivalent. blast radius to the postflight checkI 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. 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 .