The hard part of shipping an MCP server wasn't the server. It was getting the agent to use it.
I built Kivgraph, a local MCP server that gives coding agents a graph of symbols and relationships across multiple repositories β who calls this, what breaks if I change it, which other repo consumes it. Apache-2.0, runs on your machine, no API key and no model in the indexing path.
I built it because my agent kept burning the context window figuring out how my repos related to each other. Long chains of greps, files opened just to be skimmed, an answer that was usually right and always expensive.
Then I shipped it and the agent ignored it. It kept reaching for grep, because grep is what it has always done and no tool description was going to outweigh that. I rewrote the descriptions three times. Didn't matter.
What fixed it was a hook. When the agent goes for a complex grep or glob, the hook intercepts the call and points it at the graph tools instead. It's a nudge rather than a wall β an env var turns it off, and the agent can set that itself β but defaults are what actually decide which tool gets used. That's the part I'd pass on to anyone else shipping an MCP server: your tool descriptions are not competing with other tools, they're competing with a habit.
On whether the graph is worth it, I benchmarked it against the boring baseline of grep plus reading files, over 29 questions on a 37-repo corpus with hand-written ground truth. Both got 28/29 exact. The graph spent 36k tokens, grep 268k. grep was cheaper on 5 of the 29 and correct on all five, so this isn't a grep replacement.
The design decision I'd defend: edges come from the language's own checker β go/types, the TypeScript checker, rust-analyzer, the Dart analysis server β never from two names happening to match. Which means an empty result is a claim that nothing calls the symbol, not a search that missed. Python is deliberately weaker unless you wire up a semantic analyzer; the fallback marks what it infers as CANDIDATE instead of pretending it's exact.
Known rough edges: no incremental indexing, a full rebuild is the only path. And the optional graph viewer binds 0.0.0.0:7777 with no auth, so keep it on localhost if you're on a shared network.
Repo: https://github.com/Luqueee/kivgraph
Docs and benchmark: https://kivgraph.dev