This is the second post in a series about my journey building DevIndext and exploring how coding agents can benefit from better context.
- Part 1: Why Coding Agents Need Better Context
- Part 2: The Index Before the MCP Server
- Part 3: From a Working Project to a Release - WIP
In the last post, I introduced the problem DevIndext is meant to solve: coding agents need focused, trustworthy context from real repositories. This post is about how I approached building it.
The short version is that I resisted starting with an MCP server. MCP is useful, and I wanted DevIndext to work with coding agents, but it is not the architecture. If a system can only be used through one agent protocol, it becomes very easy to put the real logic in the wrong layer and hard to tell whether the underlying tool is useful on its own.
So DevIndext started as a local index and a command-line application.
Start With Boundaries
The initial .NET solution established deliberately boring boundaries: a language-neutral core, generic indexing, SQLite storage, and a CLI. I reserved separate layers for language plugins, Git integration, context retrieval, and the eventual MCP adapter. I did not create those boundaries just to have a diagram. They keep C# parsing, Git commands, and an agent protocol from leaking into the domain model.
The core understands repositories, documents, symbols, references, relationships, commits, and work items. It does not know about Roslyn or any particular LLM. That separation let me add features one at a time while keeping every stage independently useful and testable.
SQLite was the practical choice for the first version. The index lives next to the repository as derived data:
my-repository/
src/
.devindext/
config.json
metadata.json
index.db
There is no hosted database to provision and no service to keep running. If an index is stale or damaged, I can rebuild it from the source repository.
Build Up the Useful Parts
The first completed phases covered repository scanning, configuration, hashing, and incremental indexing. DevIndext tracks added, modified, deleted, and unchanged documents, so an ordinary index operation does not need to process an entire repository again.
With that foundation in place, I added the first language plugin: a Roslyn-backed C# indexer. It extracts an initial set of symbols, references, and structural relationships such as inheritance, interface implementation, containing types, method invocations, and referenced types. I was aiming for useful structural understanding, not a promise to model every semantic edge case in the first release.
The query engine came next. Before MCP was involved, the CLI could already answer focused questions:
devindext find symbol CustomerService
devindext references CustomerService
devindext callers CustomerService.CreateCustomer
devindext implementations ICustomerRepository
devindext search text "authentication"
That was an important checkpoint for me. If those commands were not useful at a terminal, putting an agent protocol in front of them would not solve the actual problem.
Context Is More Than Code
Once structural retrieval was working, I added a global workspace catalog and Git context. The catalog lets me group related repositories without centralizing their index data. Git indexing adds commits, changed documents, and detected work-item identifiers to the local picture.
The context engine is where those pieces begin to work together. Given a symbol, it performs a bounded, deduplicated traversal through the relationships and includes indexed Git history for the target document. It also records why a related item was included, along with its source location and traversal depth.
That feels different from asking an agent to perform a chain of searches and hoping it does not lose the thread. It is not magic. It is a structured starting point that I can inspect and verify.
Add MCP Last—and Keep It Thin
With the query and context services in place, the MCP adapter could stay a narrow translation layer:
MCP request -> query/context service -> bounded DTO -> MCP response
The server exposes tools for catalog status, symbol discovery, references, callers, implementations, text search, documents, direct relationships, Git history, and multi-step context. It also provides incremental refresh operations for a repository or a workspace.
The more important part is what it does not do. The MCP server does not own the SQLite schema, parse source files, invent its own retrieval behavior, or run as a long-lived service. The CLI and MCP server use the same services, which keeps behavior consistent and gives me a practical way to test each layer.
Where the Phases Stand
The core domain, storage, language contract, capability model, C# indexer, incremental indexing, query engine, workspace catalog, Git context, retrieval refinements, MCP adapter, and context retrieval are complete. That is a lot of ground to cover, but the order mattered. Each phase made the next one simpler instead of creating shortcuts I would need to clean up later.
The final phase is now underway: production hardening and release preparation. That includes benchmarks, diagnostics, packaging, installation guidance, repeatable CI validation, and the less glamorous work that decides whether a tool is ready to live outside the repository where it was built.
Conclusion
The order of these phases was intentional. By making indexing and retrieval useful before adding the MCP adapter, I ended up with a tool that is practical from both the command line and an agent workflow. The final work is about proving that foundation is dependable enough to use beyond my own machine.
That is what I will cover next.
As always, keep learning and keep building.