opencode CodeGraph: Pre-Indexed Code Intelligence for Faster Agent Work — PupPal

2026-08-09

opencode CodeGraph: Pre-Indexed Code Intelligence for Faster Agent Work — PupPal

Every coding agent has the same bottleneck. It is not the model's reasoning — it is the cost and latency of discovering code. A long-lived agent editing a large repository spends most of its context and most of its tool calls on the same primitive operations: grep for a symbol, read a file, grep for its callers, read another file, repeat. Each round trip is cheap in isolation, but they add up to a tax that dominates real sessions — and on API-metered models the tax is literal dollars. PupPal's team hit this wall across every agent harness it runs, from Claude Code to opencode to Hermes Agent itself, and the solution that finally stuck is a pre-indexed code knowledge graph served over the Model Context Protocol.

CodeGraph (58k stars) builds a local, queryable knowledge graph of your codebase with tree-sitter, stores it in SQLite with FTS5 full-text search, and exposes it to agents as an MCP server. Because the surface is MCP, the same index works for any agent that speaks the protocol: Claude Code, Cursor, Codex CLI, opencode, Gemini CLI, AntiGravity, Kiro, and Hermes Agent. This post is the practical integration guide — how to wire CodeGraph into opencode specifically, what the tools actually do, how to keep the index in sync with your edits, and why one well-aimed tool beats a menu of narrow ones.

Why Grep-and-Read Is the Hidden Tax on Every Agent Session

Before touching configuration, it is worth making the cost model explicit, because it explains every design decision CodeGraph makes. When an agent needs to answer "how does mutateElement reach renderScene?", the naive path is:

  1. grep "mutateElement" — a location list, no bodies.
  2. read the definition file — now you have one function.
  3. grep for callers of that function — locations again.
  4. read the caller file — and so on, one hop at a time.

Each hop is a separate tool call: model tokens to form the call, a round-trip, and — worst of all — the results of every hop land in context and stay there. The session fills with partial file reads, location lists, and intermediate state. Measured agent behavior shows the majority of tool calls in a typical editing session are exactly this discovery loop, and a large share of the context budget is consumed by the fragments it produces. On a 200k-token context window with a large monorepo, the agent can exhaust its budget on discovery before it ever writes a line of code.

The fix is not a faster grep. The fix is to invert the architecture: index the codebase once, up front, into a structure that already contains definitions, callers, callees, call paths, and dependency relationships — then let the agent ask the index directly. One query returns what five read-grep-read cycles would have returned, in one tool call, with far fewer tokens. That is the entire premise of CodeGraph, and it is why the integration story starts with "index once, query forever."

Step 1: Install the CodeGraph CLI

CodeGraph ships as a self-contained binary — a bundled Node runtime, nothing to compile, no native build. The installer works on Windows, macOS, and Linux, x64 and arm64 alike. The one-command install is:

npx @colbymchenry/codegraph

If you do not have Node.js, there is a plain installer for your OS (we use the Windows PowerShell installer in the PupPal monorepo) that grabs the right build and puts codegraph on your PATH. Either way, the CLI bundles everything it needs — the docs are explicit that "no Node.js required — one command grabs the right build for your OS" — and nothing is compiled on your machine.

Verify the install in a fresh terminal:

codegraph --version

The installer only puts the binary on your path; it does not touch your current shell, so open a new terminal before proceeding.

Step 2: Wire CodeGraph Into opencode (and Every Other Agent)

This is the step that connects the index to your agent, and it is where opencode's MCP support does most of the work. The CodeGraph installer auto-detects installed agents — Claude Code, Cursor, Codex CLI, opencode, Hermes Agent, Gemini CLI, AntiGravity IDE, and Kiro — and writes each one's MCP server configuration for you:

codegraph install

Run it, pick opencode from the list (or use --target opencode for non-interactive setups), and it writes the MCP entry into your opencode config. For opencode, that means an entry in opencode.json (or opencode.jsonc) under the mcp field. The resulting config looks like this:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "codegraph": {
      "type": "local",
      "command": ["codegraph", "serve", "--mcp"],
      "enabled": true
    }
  }
}

The shape follows opencode's standard local-MCP schema: type: "local" tells opencode this is a child process it spawns, command is the argv array to launch (you can also pass ["bun", "x", "..."]-style commands or any runtime you like), and enabled: true activates it at startup. If you ever need to pause it, flip enabled to false without removing the entry — useful when you want to compare sessions with and without the index. You can also pass an environment object for server env vars and a timeout in milliseconds (default 5000) for tool discovery.

CodeGraph also writes a short marker-fenced section into the agent's instructions file — AGENTS.md for opencode — so subagents and non-MCP harnesses learn the codegraph explore CLI command. That matters more than it sounds: even without MCP, a subagent told "run codegraph explore <question>" gets the same indexed lookup through the terminal. codegraph uninstall removes everything cleanly.

If you prefer to wire it by hand — say, in a CI image or a team standard config — the manual equivalent for any agent is to register the server in the agent's MCP config with the stdio transport:

{
  "mcpServers": {
    "codegraph": {
      "type": "stdio",
      "command": "codegraph",
      "args": ["serve", "--mcp"]
    }
  }
}

For opencode specifically, keep the mcp field form above; the type: "local" + command array is the opencode-native way to declare a stdio child process.

Where should that config live? opencode's configuration is layered the same way most tools handle it: a global config at ~/.config/opencode/ (or %APPDATA%\opencode\ on Windows) applies to every project, a project-level opencode.json in the repo root applies to that workspace, and the two merge — project values win over global ones. For CodeGraph, the project-level file is usually the right home for the mcp entry, because the server only does useful work in repos that have a .codegraph/ index. A global entry with enabled: false and a per-repo override with enabled: true is a clean pattern for teams that want the config checked in once and activated per project. Because opencode ships MCP tool definitions into the model's context on every request, keeping servers that are not applicable to the current repo disabled is not just tidy — it saves tokens on every single message.

You can verify the wiring without opening a session: opencode lists the tools it loaded on startup, and codegraph status (or the codegraph_status MCP tool) confirms the index is healthy and reachable. If you do not see the tools, the usual suspects are a stale opencode process (restart it — MCP servers load at startup), a command that is not on the PATH of the process opencode spawns, or a repo with no .codegraph/ directory (in which case the server intentionally exposes nothing).

Step 3: Initialize a Project With codegraph init

Wiring the agent and indexing the code are two separate steps, and this is the one people forget. Installing the CLI and registering the MCP server does not index anything. Per project, run:

cd /path/to/your/repo
codegraph init

codegraph init creates the local .codegraph/ directory and builds the full graph in the same command — one step, done. The index is a SQLite database (codegraph.db) plus support files, and it lives in the repo's .codegraph/ folder, which CodeGraph configures to be git-ignored (the directory keeps only a .gitignore that excludes everything except itself, so the database, daemon logs, and sockets never appear in diffs). In PupPal's monorepo, that database sits at roughly 300 MB after indexing the Flutter app, the Rust bridge, the Dart packages, and the Cloudflare worker — a substantial graph, and queries against it are sub-millisecond.

What actually gets indexed is controlled by ignore files. CodeGraph respects a .graphifyignore at the repo root; PupPal's excludes the usual suspects — .dart_tool/, build/, ios/Pods/, android/.gradle/, and generated Dart files (*.g.dart, *.freezed.dart, *.gen.dart, *.mocks.dart) — so the graph contains hand-written code, not build output. That single decision keeps the index small, relevant, and cheap to sync.

Restart opencode after the install/init sequence so the MCP server loads. When a .codegraph/ index exists, the agent sees the CodeGraph tools; in a workspace with no index, the server announces itself inactive and lists no tools — the agent just works normally with its built-in tools, and indexing stays your decision. No breakage, no noisy errors, no tools to ignore.

What the Tools Actually Do: One Tool by Default

Here is the design choice that makes CodeGraph different from every other code-intelligence MCP server. By default, the server exposes a single tool: codegraph_explore. It is Read-equivalent — you give it a natural-language question or a bag of symbol and file names, and it returns:

  • the verbatim, line-numbered source of the relevant symbols, grouped by file — the same shape the Read tool gives you;
  • the call paths among them, including dynamic-dispatch hops like callbacks, React re-renders, and JSX children that grep cannot follow;
  • a blast-radius summary of what depends on the symbols.

One call usually answers the whole question. This is deliberate, and the maintainers are explicit about why: measured agent behavior showed that one well-aimed tool steers agents to a direct answer better than a menu of narrower ones — fewer mis-picks — and agents reach for it both when answering questions and while editing code. You do not have to teach the model which of nine tools fits the query; there is one, and it is the right one for almost everything.

Seven more tools exist and remain fully functional, but they are unlisted by default — everything they return already arrives inline on a codegraph_explore response:

| Tool | Purpose | |------|---------| | codegraph_node | One symbol's source + caller/callee trail, or a whole file read with line numbers (Read-parity). Returns every overload's body for an ambiguous name. | | codegraph_search | Find symbols by name across the codebase (locations only) | | codegraph_callers | Find what calls a function | | codegraph_callees | Find what a function calls | | codegraph_impact | Analyze what code is affected by changing a symbol | | codegraph_files | Get the indexed file structure (faster than filesystem scanning) | | codegraph_status | Check index health and statistics |

Re-enable any of them with the environment variable that controls the tool allowlist — a comma-separated list of short names that replaces the default. The list is a different way of saying "the single-tool default is the product; the rest are power tools for when you need them."

A concrete session makes the shape of a query clear. In PupPal's repo, asking codegraph_explore "how does a photo check-in flow from the Rust bridge into the UI?" returns, in one response: the pi_bridge.rs functions that expose the session API, the Dart RustBridgeApi methods that call into them, the call path across the package boundary, and the blast-radius list of UI files that consume the stream. No grep, no guessing file names, no partial reads — the agent gets the source it needs and can answer immediately. That is the experience the rest of this post is trying to reproduce in your own setup.

Keeping the Index Honest: Sync on Edit

A pre-indexed graph is only useful if it does not lie. CodeGraph solves this with a three-layer sync that keeps the index in step with your code, and — crucially — makes sure the agent never gets a silent wrong answer in the brief window between an edit and the next sync:

  1. Incremental re-indexing. Edits to indexed files trigger targeted updates to the affected symbols rather than full rebuilds.
  2. File watching. The server watches the workspace; when you save, the affected parts of the graph refresh.
  3. Honest staleness. If the agent queries a region that has changed but not yet been re-indexed, the server says so instead of returning stale source silently.

That third layer is the one that separates this from a naive "index once, hope for the best" tool. In PupPal's daily workflow — agents editing Dart and Rust files in the same monorepo while a daemon keeps the graph warm — the index is effectively never stale enough to matter, and the agent's answers cite line-numbered source that matches what read_file would show. We verify this the same way we verify any agent claim: read the file. It matches.

Token Economics: Why the Single Tool Wins

The numbers that matter for an API-metered agent are tokens per question and tool calls per task. The third-party measurements around CodeGraph consistently land in the same range: roughly 35% lower API cost and 70% fewer tool calls on typical code tasks, compared with grep-and-read loops. The mechanism is easy to understand once you have seen a session: a discovery loop that used to be five tool calls and a screenful of partial reads becomes one codegraph_explore call returning exactly the source the model needs to answer — nothing more. Fewer calls means fewer model round-trips; tighter payloads mean less context pollution; both mean the model's budget goes to reasoning, not archaeology.

There is a caveat every opencode user should hear, straight from opencode's own MCP docs: every MCP server you enable consumes context space. The tool definitions themselves occupy tokens on every request, and servers that return large payloads can eat context fast. CodeGraph's single-tool default is precisely an answer to this — one tool definition instead of nine — and its payloads are bounded by relevance rather than by file scans. The guidance stands though: be selective about which MCP servers you keep enabled. In PupPal's opencode config, CodeGraph is one of only a handful of servers, and it is the only code-intelligence one. The rest of the intelligence comes from the index, not from more servers.

PupPal's Setup: One Index, Every Harness

PupPal runs several agent harnesses against the same monorepo — opencode for day-to-day edits, Claude Code for reviews, Hermes Agent for the automation pipeline — and CodeGraph's MCP surface is what makes one index serve all of them. The .codegraph/ directory at the repo root is a single source of truth; whichever agent launches codegraph serve --mcp reads the same SQLite graph. The repo's AGENTS.md documents the arrangement: the four indexed projects (the Flutter app, the Dart packages, the Rust bridge, the Cloudflare worker) are all reachable through codegraph_explore's projectPath parameter, so an agent working in any subproject gets the same indexed lookup without extra setup.

The projectPath parameter deserves a callout for monorepo users: it lets one MCP server serve multiple indexed projects, and the agent names which project it is exploring. The same pattern applies to opencode — the server inherits the workspace root, and a monorepo with several codegraph init-ed subprojects can be queried per project.

For agent authors, the operational rules we settled on are simple:

  • Index once per project with codegraph init; re-init when the project layout changes dramatically.
  • Keep .graphifyignore current. Generated code in the index is noise; it dilutes every query. Exclude build outputs and generated files aggressively.
  • Trust but verify. Treat the index as a fast path to source, not as a fact engine — the line-numbered source it returns is the ground truth you verify against.
  • Let subagents use the CLI. The AGENTS.md marker section means even a subagent without MCP access can run codegraph explore in the terminal.

From Index to Faster Engineering

The end state is boring in the best way: the agent just knows where things are. When a PupPal session needs to trace how a photo check-in event flows from the Rust bridge through the Dart packages to the UI, codegraph_explore returns the call path and the verbatim source in one call — the same trace that once consumed a dozen grep-and-read round trips. The savings compound across a long session: less context spent on discovery means more context for actual reasoning, which means fewer corrective edits, which means fewer rebuild cycles. On a Flutter+Rust codebase where every rebuild is a non-trivial cost, getting the agent's first draft right matters even more.

CodeGraph's own docs frame it as "fewer tokens, fewer tool calls, 100% local" — and the local part is worth underlining. The index, the SQLite database, the daemon, and the MCP server all run on your machine. No code ever leaves the repo to be indexed, which makes the whole setup viable for the same reason PupPal's privacy story matters: the sensitive part of the pipeline stays local, and only the parts that must leave — the model prompts — do.

If you run opencode (or any MCP-capable agent) against a codebase that is bigger than a few files, the sequence is short: install the CLI, run codegraph install and pick opencode, codegraph init in each repo, restart the agent. Ten minutes of setup buys you an agent that stops hunting for code and starts answering questions. Given what discovery loops cost in tokens and time, that is the cheapest performance upgrade an agent setup can get.