Stop letting your AI agent grep its way around your codebase
- 9 minutes read - 1737 wordsI’ve been using coding agents like Claude Code a fair bit recently, and for the most part I think they’re great tools, but not without flaws. The agent doesn’t understand my codebase; It just reads it. Those two things are not the same.
How an agent finds things by default
Ask an agent to rename a method, or to find everywhere a particular type gets used, and watch what it actually does. It greps. It runs a regex over your files, gets back a wall of text, then opens a few of those files in full to figure out which matches are real and which are noise, and quite often it’ll grep again with a slightly different pattern because the first one was too greedy.
On a small project, fine, who cares. But on anything real this is death by a thousand reads. Every file it opens just to “have a look” costs tokens, and eats into the context window it’s got to work with. I watched it open a 600 line file to confirm a single usage on line 42, and I just thought “this cost me money because it’s doing it in an inefficient way”.
Stupid? Yes. Unavoidable? Probably not. For now, though, it’s doing the only thing it can with the tools it’s got. A coding agent on its own has a filesystem and a text search and that’s about it. It has no idea that GetUser on line 42 is the same symbol as the one declared three files away. To a regex it’s just five characters that happen to crop up in a lot of places.
Your IDE has known this for decades. It’s got a full semantic model of your project sat in memory: every symbol, every reference, every type, all properly resolved. That’s the whole point of an IDE. So you do start to wonder why the agent is out here doing string matching when there’s a tool running on the same machine that already knows the answer.
Enter the JetBrains MCP server
MCP, the Model Context Protocol, is the plumbing that lets an agent call out to external tools in a structured way. JetBrains ship an MCP server with their IDEs, so you can hand Claude Code (or any MCP-aware agent) a direct line into the IDE’s brain.
All of a sudden the agent isn’t stuck with “read file” and “search text”. It can ask the IDE things like:
- Find every usage of this symbol, and get back the actual references, properly resolved, not a regex’s best guess.
- Go to the definition of this thing, straight to the source, no rummaging about.
- Tell me about this symbol: its type, its signature, where it lives, without reading the file at all.
- Rename this safely across the whole project, a real refactor rather than a find and replace.
- What are the problems in this file? The IDE’s own inspections and warnings, handed straight over.
- Reformat this to the project’s style, instead of the agent guessing what my conventions are.
The big one for me, by a mile, is navigation that doesn’t cost a fortune in tokens. When the agent wants to know where a method is used, it asks the IDE and gets a precise list back. It doesn’t open six files to find out. And that adds up. Less rubbish in its context means it stays sharper for longer, and I’m not sat there watching it slowly fill its head with the contents of files it didn’t really need to see.
The refactoring angle
The other thing that won me over was watching it rename things properly.
A rename done by string replacement is a bit risky. The agent has to find every occurrence, decide which ones are the symbol and which are a coincidence (a comment, a string, some unrelated variable with the same name two modules over), and then edit each one without breaking anything. Most of the time it gets it right. But “most of the time” is exactly the failure mode you don’t want in a refactor, because the misses are silent. You don’t find out until it won’t compile, or worse, until it does compile and behaves a bit wrong.
A rename through the IDE just doesn’t have that problem. The IDE already knows exactly which references belong to the symbol, because it resolved them properly rather than textually. It’s the same operation you’d happily trigger yourself from the refactor menu, except now the agent can drive it. The agent decides what to rename and the IDE handles doing it correctly. That’s a much better split than asking a language model to be really careful with a regex and hoping for the best.
What about Serena?
Before I landed on this, I had a go with Serena, which is an open source MCP server that gives an agent semantic code tools too. It’s a sensible bit of kit and I want to be fair to it: it was perfectly fine. It hangs off the Language Server Protocol, so under the bonnet it’s talking to the same sort of language servers that power “go to definition” and “find references” in most editors. That means it can do the headline thing I cared about, which is letting the agent find symbols and their usages without reading half the repo to get there. For a lot of people that’ll be plenty, and the fact it’s open source and editor-agnostic is appealing.
Where it fell a bit short for me was feature completeness. LSP gives you a solid core (symbols, references, definitions, that kind of thing), but a full JetBrains IDE is doing a lot more on top of that. The refactoring engine is the obvious one. A LSP-backed rename is decent, but it isn’t the same as JetBrains’ rename, which understands the wider context and handles the awkward edges far more confidently. Then there’s the whole inspections and warnings side, which is a big part of why I reach for the MCP server in the first place, and that just isn’t really there in the same way. Same goes for reformatting to the project’s actual style, navigating modules and dependencies, and all the other things JetBrains have spent years polishing.
So it’s not that Serena is bad, because it isn’t. It’s that JetBrains is sat on top of a couple of decades of IDE features, and when the agent borrows that brain it’s borrowing a noticeably richer one. If you’re not a JetBrains person, Serena is a very reasonable place to start. I just happen to already have the better toolbox open on my second monitor.
Setting it up
Good news is it’s quick. Roughly:
- Install the MCP plugin in your JetBrains IDE. Recent versions ship with MCP support, so have a look in the plugins marketplace for the MCP Server plugin if it isn’t already there, and turn it on.
- Point Claude Code at it. Register the JetBrains MCP server with your agent, which in Claude Code is an entry in your MCP config. The IDE exposes the server and the agent connects to it.
- Have the IDE open on the project you’re working in. This is the bit that trips people up. The MCP server is backed by a running IDE instance with your project indexed, and that’s the whole trick. The semantic model the agent borrows is the one the IDE has already built. No open project, no brain to borrow.
Once it’s connected you don’t really do anything differently. You ask for the same things you always did. The difference is in how it goes about getting them, and you do notice it: fewer files cracked open to answer simple questions, refactors you actually trust, and warnings caught before it tells you it’s finished rather than after.
A small habit that pays off
The one nudge I’d add is to spell it out in your instructions. Agents will happily fall back to grep if you let them, purely because it’s always there. I keep a line in my setup along the lines of “prefer the JetBrains MCP server for navigation, refactoring and code analysis, and check for inspection warnings before considering a task done”. It’s a tiny thing, but it’s the difference between the agent reaching for the IDE first and forgetting it even has it.
The catch: it’s tied to a specific IDE
There’s one wrinkle worth being upfront about. The MCP server isn’t some general “JetBrains” service that knows about everything you’re working on. It’s tied to a specific running IDE instance and the project it’s got open.
For a lot of work this doesn’t really matter. The JetBrains IDEs share a lot of their guts, so something like a .NET backend with a React front end sits perfectly happily in Rider on its own, because the JavaScript and TypeScript support is right there in the box. You don’t need a separate IDE for the front end. So the “different products” thing isn’t the problem people sometimes assume it is.
Where it nips at me is that I work for a consultancy, so I hop between clients and languages every now and again. One stint might be .NET in Rider, the next something that’s a better fit for GoLand or PyCharm. That means more than one JetBrains IDE installed, and the MCP setup is per IDE, so each one is its own install, its own server to register, and a moment of “which one is the agent actually talking to” when I switch. It’s honestly not the end of the world, more a good thing to be aware of than a dealbreaker. If you live in one IDE all day you’ll likely never notice it. If you swap around like I do, it’s just a bit of extra housekeeping to keep in mind.
Was it worth it?
For me, easily. The setup is a few minutes and what you get back is an agent that spends its tokens thinking about my actual problem instead of re-reading my codebase to remember what’s in it. It navigates like it understands the project, because for as long as it’s connected it’s borrowing something that does.
If you’re already living in a JetBrains IDE and running an agent next to it, you’ve got a semantic model of your whole project sat right there going spare. Seems a bit daft not to let the agent use it.