Beyond Grep: Testing Codebase Memory MCP on .NET
I’ve always been a bit frugal. Even as a kid playing Warcraft III, I hated losing a single paladin. Apparently, that habit has followed me into AI coding. Only now I’m saving tokens.
When I ask a simple question about my repo and see the harness(Claude Code, Codex, etc.) start “grepping” through half the codebase, I can almost smell the tokens burning. And you should see my face when Claude Code decides it also needs to start decompiling NuGet packages. That’s usually when I hit cancel.
I try to overcome this pain by providing as much context as possible before the task even starts. Adding the file paths I think the LLM might need to my prompts has become part of my daily routine. But it’s tiresome, so it’s time to try something new: Code intelligence tools.
Code intelligence
The code intelligence idea seemed pretty simple. You give the harness a map of your codebase: classes, methods, and the relationships between them. Then, when you ask a question, the LLM already has a good starting point.
My Visual Studio has been doing this for years. I can jump to a definition, find all references, or see who calls a method almost instantly. So why shouldn’t my coding harness get the same kind of help?
How does it work?
Code intelligence tools analyze the source code to identify its members and relationships, then make that information queryable. Making it queryable usually means some sort of MCP server that your LLM harness (Codex, Claude Code) can call during coding tasks.
A Cambrian explosion of code intelligence
Once you start looking for these kinds of MCP servers, you quickly discover how many different approaches there are: persistent code graphs, language-server integrations, MD files, repository maps, and tools that expose these capabilities through MCP.
It feels again, again, again like a Cambrian explosion of code intelligence.
The difficulty is that “understands your codebase” can mean many things. I tried a few tools, and they usually struggle in different ways; some had problems with my medium-sized repo, and it didn’t finish linking after 30 minutes. Some were only slightly better than “grep,” and many were a few weeks old.
Codebase Memory MCP
Codebase Memory MCP describes itself as a local code-intelligence engine that builds a persistent knowledge graph of a codebase and exposes it to the coding harness through MCP. Its pipeline uses tree-sitter for parsing, including support for C#. Local means all processing happens on your machine, and you can index the codebase directly from the CLI.
Moreover, Codebase Memory is an eight-month-old project that has already gathered around 42k stars. Compared to the alternatives, it seems to be gaining quite a bit of traction.
How the architecture fits together
Let’s get a bit technical. The pipeline to parse, store, and consume a model of your codebase is straightforward:
Tree-sitter identifies code structures such as classes, methods, imports, and calls. For customer.Save(), it finds the receiver (customer) and method (Save).
Codebase Memory’s Hybrid LSP layer then works out which declaration the call refers to. It uses language-specific type resolution inside the indexer. C# support depends on the features and dependencies your project uses. The results form a graph: symbols become nodes, and relationships such as calls and imports become edges.
The knowledge graph (backed by SQLite) persists it as a SQLite database.
MCP Server lets the Claude Code, Codex, or your favorite harness find symbols, trace calls, and retrieve relevant source code.
How indexing works
As I mentioned, you can index a repository by running a CLI command against it. Codebase Memory also supports indexing directly through your coding harness. In fact, using Claude Code or Codex is the recommended approach.
Just ask your favorite harness to “Index this project” and hope it picks the right MCP tool. If you have multiple code-intelligence tools connected, though, you may need to explicitly name the Codebase Memory server.The other route is the CLI:
codebase-memory-mcp
cli
index_repository
--repo-path <path>
--mode fast
--persistence trueEither way, all processing happens locally.
What does the harness actually get?
Once you index the repository, the harness gets a set of tools for navigating and understanding the codebase. It can find a symbol and determine which project it belongs to, trace incoming relationships to identify callers, follow outgoing calls, and read the source behind anything relevant to the proposed change.
The tools map quite naturally to the questions I keep asking about my code. The methods include search_graph to find symbols, trace_path to follow relationships between them, and get_code_snippet to let the harness inspect the actual implementation.
For a wider view, get_architecture summarizes the codebase, while query_graph supports more specific questions about relationships using Cypher-like queries.
You can find the full list in the MCP tools documentation.
Why AGENTS.md and CLAUDE.md matter
There is one catch: all of this helps only when Codex or Claude Code actually uses these tools. If you want the harness to rely on the indexed graph instead of falling back to its usual code search, you need to be stricter about when and how it should use them.
I started with a fairly weak instruction along the lines of “prefer Codebase Memory”, but that didn’t reliably change the harness’s behavior. What worked better was being much more explicit:
“If unsure whether Codebase Memory is useful, try Codebase Memory first.”
I keep instructions like this in AGENTS.md or CLAUDE.md.
Note:AGENTS.md or CLAUDE.md are markdown files in the repository that give the coding harness project context and instructions. I think of them as a place to write down how I want the harness to work, so I don’t have to repeat myself every time I start a new session.
Don’t forget that LLMs are non-deterministic, so keep an eye on the harness’s actual tool choices to see whether our beloved LLM really followed your instructions.
How to work in teams
My initial assumption was that the index is private, much like Visual Studio’s IntelliSense cache. By default, Codebase Memory stores its SQLite database locally, so each developer builds their own index.
However, index_repository has a persistence option that stores the index under .codebase-memory/graph.db.zst. This means you could commit the index and share it with the team.
I’m not convinced this is a good idea. The index can become stale as soon as the code changes, it’s stored in Git as an opaque binary file, and merge conflicts are difficult to resolve.
The same issue applies to manage_adr. It lets the harness store architectural knowledge that cannot be derived from the code alone. Despite the ADR name, this is not a Markdown file in your repository. It is stored in the local SQLite database.
Get Marek Sirkovský’s stories in your inbox
Join Medium for free to get updates from this writer.
So sharing these ADRs also means sharing graph.db.zst, with the same drawbacks. For architectural knowledge that should live with the code and be shared by the team, I’d rather put it in AGENTS.md or another normal Markdown file.
Hybrid LSP is limited
Given that it uses a hybrid LSP approach rather than a Roslyn-based analyzer, I ran a few experiments with newer C# features. One issue in the latest version is that a method declared inside a C# 14 extension block did not appear in the graph after indexing. I reported the problem here.
This may be one consequence of using a hybrid analysis approach rather than Roslyn’s full semantic model. In my test, the tool did not capture this newer C# construct, so I would be cautious about assuming complete coverage of recent language features.
Cross-repository edges
Another limitation is missing cross-repository edges. These are relationships between code elements that live in different repositories. A classic example is an HTTP call between microservices.
Codebase Memory claims it can match an HTTP call in one repository with the corresponding route in another and create an edge between them in the graph.
However, when I tested this claim with the following C# example, Codebase Memory did not detect this type of relationship.
// API project
app.MapGet("/customers/{id}", (int id) => Results.Ok(new
{
Id = id,
Name = $"Customer {id}"
}));
// Client project (in a different so)
HttpClient.GetAsync("http://localhost:5000/customers/42")The API side works as expected: MapGet produces a Route node for GET /customers/{id}. The client side, however, is not represented in the graph at all: GetAsync("http://localhost:5000/customers/42") produces no corresponding node or relationship.
Just for fun, I wrote the same logic in JavaScript, and it gets a bit further.
fetch('http://localhost:5000/customers/42')Here the extractor recognizes fetch and emits Function -HTTP_CALLS-> Route with the correct URL.
Support for various harnesses
Codebase Memory offers broad harness support, integrating with 45 tools, including Claude Code, Codex CLI, Gemini CLI, Cursor, Windsurf, OpenCode, and others. Its installer detects supported environments and configures MCP connections, durable instructions, skills, and harness profiles while preserving user changes and respecting each client’s documented capabilities.
120 times fewer tokens?
Codebase Memory claims that its graph-based approach can use up to 120 times fewer tokens than navigating a codebase without the MCP server.
That may be possible for complex tasks involving many symbols and relationships, but I did not see that kind of improvement in every case.
In a simple test where I asked the harness to find a class, using Codebase Memory consumed roughly twice as many tokens as a straightforward grep-based search.
I also tried a few broader “show me the impact of this change across my multi-repo codebase” prompts, where I expected the graph-based approach to provide a clearer advantage. In those tests, however, Codebase Memory still consumed slightly more tokens than the baseline approach.
On the other hand, over several weeks of testing, I didn't see Claude Code or Codex resort to decompiling NuGet packages to get missing dependency information.
What about the alternatives?
Several projects are worth comparing against the same practical questions. In my research for this blog post, I looked more closely at two other code intelligence tools.
GitNexus
GitNexus is getting a lot of attention these days, trending on both Product Hunt and GitHub. I originally planned to write about it, but since it’s free only for non-commercial use, I decided to focus on Codebase Memory instead.
Conceptually, GitNexus belongs to the same family as Codebase Memory and Grafel. It indexes a repository, turns the code into a graph of symbols and relationships, and gives a harness MCP tool.
When I ran the same C# 14 extension-block test, GitNexus recognized the syntax but could not resolve a call to the extension method from another method in the same solution.
I also repeated the API-to-client test with GitNexus. It recognized the API endpoint, but it did not represent the client call or create a relationship between the two.
I did not measure GitNexus’s token consumption against my larger commercial codebase, as doing so would have violated its non-commercial license.
Grafel
Grafel is a local code-knowledge-graph daemon. You register one or more repositories in a group, run the indexer, and query the resulting graph through its MCP server, CLI, or dashboard. It stores a graph for each repository and adds an overlay for relationships that cross repository boundaries.
Regarding support for the new C# 14 extension syntax, Grafel also failed to identify the extension method.
I then tested Grafel against the same small provider/consumer projects. It recognized the HTTP call, but the exported graph contained no direct edge connecting the client to the API, so it failed this test as well.
The biggest issue I encountered with Grafel was scalability. On a larger codebase of roughly 30 projects and 600,000 lines of code, the linking phase never completed, so I couldn’t test token consumption.
The NuGet wall
After looking at those limitations, there’s one more boundary worth testing: what I call the NuGet wall.
The test is deliberately simple:
// provider solution:
public static IServiceCollection AddMyCache(
this IServiceCollection services,
bool enableDistributedCache)
{
services.AddMemoryCache();
if (enableDistributedCache)
{
services.AddDistributedMemoryCache();
}
return services;
}
// consumer solution place in a different git repository
services.AddMyCache(enableDistributedCache: true);
The method AddMyCache lives in a git repository, is distributed as a NuGet package, and is called from the consumer.
Unfortunately, all three tools failed to resolve this relationship end to end. That is understandable to some extent: once the code crosses the NuGet boundary, there is no obvious mapping that says “this package came from this particular repository”. Still, the tools could use namespaces and package references.
Codebase Memory MCP finds AddMyCache in the provider, but does not create a consumer call node or a cross-repository trace.
GitNexus does not resolve the method relationship in its cross-relationship (gnx-cross) group and reports zero contract links.
Grafel gets the closest. It finds both the provider method and the consumer call and creates a weak, module-level candidate with 64 percent confidence.
All three ultimately hit the same NuGet wall, just at different stages: some fail to represent the consumer call at all, while others identify both sides but cannot reliably connect them.
Conclusion
I haven’t found what I was looking for: a code-intelligence tool that can reliably trace a .NET codebase across repositories and NuGet dependencies. I’ll keep experimenting with Codebase Memory MCP, especially for repository-local navigation. Still, my tests so far haven't shown enough of an advantage to know whether it will remain part of my long-term toolkit.








