Graph Indexer exposes eight read-only MCP tools: search_code, search_text, read_code, find_references, call_graph, change_impact, check_changes and outline. Each answers one question an AI coding agent asks while changing code, returns a compact plain-text reply of typically 400 to 1,500 tokens, and ends with a hint for the next step. The agent keeps using its own tools for reading and grepping; these are for what text search cannot answer exactly.
All eight tools are read-only. Every reply is capped, lists the best result first, and states what the index cannot see, for example same-name calls it could not bind to a definition. The index is re-synced with the files on disk before every answer, so results include edits the agent made a second ago. For how that works, see How Graph Indexer works.
The names below are the MCP tool names. Examples use generic names and show the shape of the output, not exact bytes.
| Tool | Question it answers | CLI equivalent |
|---|---|---|
search_code | Where does this behaviour live? | graph-indexer search |
search_text | Where does this text appear, and what is each match? | graph-indexer grep |
read_code | What does this code say, and where is everything it uses defined? | graph-indexer read |
find_references | Who uses this symbol? | graph-indexer refs |
call_graph | Who calls this, and what does it call? | graph-indexer callgraph |
change_impact | What does changing this affect? | graph-indexer impact |
check_changes | What did my uncommitted edit break? | graph-indexer check |
outline | What is in this file, directory or repository? | graph-indexer outline |
search_code: where does this behaviour live?
search_code finds functions, classes and types from a description in words (“where are tokens validated”) or from identifiers, and returns ranked symbols with location, signature, first doc line and matching lines. It combines name, signature, documentation and body matching with graph centrality; it uses no embeddings and no model.
Key parameters:
query(required): a behaviour description or identifiers.path: only results under this path prefix, for examplesrc/auth/.kind: only this symbol kind (function,method,class,interface,struct,enum,type,field).limit: 1 to 30, default 8.
> search_code("where are tokens validated")
3 results for "where are tokens validated" (of 41 candidates):
1. AuthService.verifyToken — method · src/auth/auth.service.ts:58-83
verifyToken(raw: string): Claims
Verify the signature and expiry of a bearer token.
62: const claims = this.jwt.verify(raw);
2. authGuard — function · src/auth/guard.ts:12-30
…
Next: read_code with the names you want (several at once); find_references / call_graph for usages.
An agent should use search_code when it knows what the code does but not what it is called. For literal strings and config keys, search_text is the better tool.
search_text: where does this text appear?
search_text is grep over every file in the repository (code, configuration and docs) that also tells the agent what each match is. A match inside code carries its enclosing function, and for an identifier it says whether the match is the definition, a reference bound to a symbol, an unbound reference, or just a comment or string.
Key parameters:
pattern(required): a JavaScript regular expression, or plain text withliteral: true.path: only files under this prefix, such assrc/orconfig/app.yaml.literalandignore_case: booleans, both defaultfalse.limit: 1 to 300 matching lines shown, default 60; totals are always complete.
> search_text("parseConfig", literal: true)
7 matches in 4 files for "parseConfig":
src/config/loader.ts
14 def │ export function parseConfig(raw: string): Config {
src/app.ts
22 → parseConfig (src/config/loader.ts) (bootstrap) │ const cfg = parseConfig(text);
docs/setup.md
40 comment │ Call parseConfig before starting the server.
If the search misses the definition of the name it was given, the reply says where the definition is, so the next call is a read instead of another search. Use search_text for literal strings, error messages, config keys and anything that is not a symbol.
read_code: what does this code say?
read_code reads functions, methods, classes, line ranges or whole files, several per call, with line numbers. Under each result it lists where every name the code uses is defined (file, line, signature, first doc line). For a symbol it also shows overrides, who uses it and which tests reference it.
Key parameters:
targets(required): up to 12 items, each a symbol (myFunc,UserService.find,path:Name), a range (src/app.py:120-180) or a file.full: return whole files even when long, up to 2,000 lines. Without it, a file over 300 lines comes back as an outline.max_lines: 5 to 1,000, default 200; longer definitions are truncated after this many lines.
> read_code(["UserService.find"])
UserService.find — method · src/users/user.service.ts:31-47
31 async find(id: string): Promise<User> {
…
Defined elsewhere (used above):
UserRepository.byId src/users/user.repository.ts:19 byId(id: string): Promise<User | null>
Used by: 4 callers in 3 files · tests: user.service.spec.ts
An agent should use read_code when it knows the name or location. It replaces the read-then-search loop with one call.
find_references: who uses this symbol?
find_references lists every use of a symbol (calls, instantiations, type uses, inheritance, decorators and value uses), grouped by file with the enclosing function and line. References are bound through scopes, imports and inferred receiver types, so a call to UserService.find is not mixed up with every other method named find. Calls made through a parent type are marked, and string-named members (such as jest.spyOn or Python getattr) are included.
Key parameters:
symbol(required): a name,Class.member,path:Nameorpath:line.kind:all(default),call,type,inherit,new,valueordecorator.path: only references in files under this path.include_tests: defaulttrue.limit: 1 to 500, default 80.
> find_references("UserService.find")
6 references to UserService.find (src/users/user.service.ts:31) in 4 files:
src/users/user.controller.ts
44 UserController.show call exact
src/admin/admin.service.ts
17 AdminService.lookup call exact (via base type)
Complete: all indexed references listed. Unbound: 2 other "find(…)" calls with receivers of unknown type; none is in a file that mentions UserService.
Each reference carries a confidence label: exact or high means it was bound through a scope, import or inferred receiver type; likely or possible means only the name matched. The footer says whether the list is complete and whether unbound same-name calls are plausible, so the agent knows when to double-check. This is the tool to call before renaming a symbol or changing its signature. On 400 nestjs symbols checked against the TypeScript compiler, it reached 99.7% precision and 96.2% recall; grep reached 12.8% precision. See the benchmarks.
call_graph: who calls this, and what does it call?
call_graph returns the callers (transitively, up to a depth) and callees of a function or method as a tree with file locations and confidence, showing how a request or a piece of data flows across files.
Key parameters:
symbol(required): same forms asfind_references.direction:callers,calleesorboth(default).depth: 1 to 4, default 2.limit: 5 to 200 nodes per direction, default 40.include_tests: defaulttrue.
> call_graph("parseConfig", direction: "callers", depth: 2)
parseConfig — src/config/loader.ts:14
callers:
bootstrap src/app.ts:20 [exact]
main src/main.ts:5 [exact]
loadTestConfig test/helpers.ts:9 [exact]
Use call_graph to understand flow across files before touching a function in the middle of a chain.
change_impact: what does this change affect?
change_impact reports the blast radius of a change: transitive dependents ordered by distance, overrides and subclasses that inherit the symbol, the tests to run, files that change together in git history, a risk level, and what the index cannot see. When the graph cannot tell, the risk reads “unknown” rather than guessing.
Key parameters:
symbols: symbols you are changing.files: files you are changing (all their symbols).diff:trueuses your uncommitted git changes as the change set.depth: 1 to 5, default 3.
> change_impact(symbols: ["UserService.find"], depth: 2)
Impact of changing UserService.find
dependents: 7 symbols in 3 files (non-test)
distance 1 (3):
UserController.show src/users/user.controller.ts:40-52
AdminService.lookup src/admin/admin.service.ts:14-25
distance 2 (4):
…
tests to run (1 file):
user.service.spec.ts
often changed together (git): user.controller.ts (9/31), …
risk: medium (7 dependents, 1 test files)
An agent should call change_impact before a signature change or a refactor, and with diff: true to review what an unfinished change reaches.
check_changes: what did my edit break?
check_changes verifies uncommitted edits without building the project. It reports syntax errors introduced by the edit, calls whose argument count no longer fits a changed signature, removed or renamed names that are still in use, callers in files the agent did not touch, subclasses that inherit changed methods, and the closest tests with the command to run them (jest, vitest, mocha, pytest, unittest, Django, go test, cargo, Maven and Gradle are recognised). It takes about 1 second on nestjs.
Key parameters:
files: only check these changed files or directories (default: all changes).base: git revision to compare with, defaultHEAD.
> check_changes()
2 files changed. Problems:
src/users/user.controller.ts:44 UserService.find(id, opts) — 2 arguments, signature now takes 1
src/admin/admin.service.ts:17 untouched caller of UserService.find
tests to run: npx vitest run src/users/user.service.spec.ts
check_changes is not a type checker, so it complements the compiler rather than replacing it. Its value is speed: it flags the call sites an edit left behind before the agent runs a slow build. In real-issue tasks, the agentic benchmark found no measurable saving from the tools; see Benchmarks for the numbers.
outline: what is in this file or repository?
outline shows definitions without bodies. For a file it returns signatures with line ranges. For a directory or the whole repository (empty path) it returns the most central symbols ranked by PageRank within a token budget, optionally centred on a task description.
Key parameters:
path: a file or directory; empty means the whole repository.focus: a task or query to centre the map on.max_tokens: 200 to 8,000, default 1,500.
> outline(path: "src/", focus: "token validation", max_tokens: 800)
src/auth/auth.service.ts
class AuthService :12
verifyToken(raw: string): Claims :58-83
src/auth/guard.ts
function authGuard(req, res, next) :12-30
…
An agent should use outline on arrival in an unfamiliar repository, or to decide which file to open. The repository map applies the ranking idea of a repo map to symbols instead of files.
How do the CLI and hooks relate to the tools?
The command line runs the same tools, which is useful for scripts and for checking what an agent sees:
npx graph-indexer search "where are tokens validated" --path src/ --limit 5
npx graph-indexer read UserService.find src/app.py:120-180
npx graph-indexer grep "parseConfig"
npx graph-indexer refs UserService.find --kind call --no-tests
npx graph-indexer callgraph parseConfig --direction callers --depth 3
npx graph-indexer impact --diff
npx graph-indexer check
npx graph-indexer outline src/ --focus "token validation" --max-tokens 800
The CLI also has files <text|glob> to find files by path, which MCP hosts already do with their own file finder, plus init, serve, index, status, hook and daemon.
Hooks make the tools work without the agent having to ask. Running npx graph-indexer init --hooks adds Claude Code hooks to .claude/settings.json that call graph-indexer hook on PostToolUse (after Edit, Write, MultiEdit, Grep, Bash or Read), SessionStart and SubagentStart:
- After an edit, the hook runs the edit check on that file.
- After a search that missed a definition, it says where the definition is.
- Once the agent is crawling (three searches or reads since the last edit), a read gets where its names are defined.
- A subagent starts with the lookup rules.
- Otherwise it stays silent, and it fails open under a deadline so it never blocks the agent.
The hook payloads of Cursor, Codex, Gemini CLI and Copilot CLI are read too, and OpenCode and Kilo Code get an equivalent plugin. A resident process keeps the index open, so hook and CLI calls answer in about 0.1 s. Setup details are in Install Graph Indexer and the Claude Code plugin page.
Frequently asked questions
Are the Graph Indexer MCP tools read-only?
Yes. All eight tools are marked read-only and idempotent. They never edit files, never run indexed code and never open a network connection. The only subprocesses are read-only git commands such as git diff and git ls-files.
Which Graph Indexer tool should an agent call first?
It depends on the question. Use search_code when you only know the behaviour, read_code when you know the name, and find_references before renaming or changing a signature. find_references is the one tool loaded up front by clients that defer MCP tools.
Can I run the tools without an AI agent?
Yes. The command-line interface runs the same tools: graph-indexer search, read, grep, refs, callgraph, impact, check and outline. It is useful in scripts and to see exactly what an agent sees.