MCP server
code-graph ships a stdio Model Context Protocol server. An agent gets callers, entry points and file:line evidence from the graph. It needs the mcp Python SDK (2.x). Use the installed cg-mcp command from Install.
Use as an MCP server
- Build the graph first, from the repo root:
cg index . --db out/graph.db. The server only reads a graph; it does not index on start. - Add the server to your client.
cg install --host cursor(orclaude,codex,vscode, …) writes the entry for you. To add it by hand, without installing cg first:
{
"mcpServers": {
"code-graph": {
"command": "uvx",
"args": ["--from", "cg-code-graph", "cg-mcp", "--db", "out/graph.db", "--tools", "core"]
}
}
}
--tools core lists a small set of tools; drop it to list all of them. A relative --db resolves from the client's working directory, usually the project root. The same entry ships as .mcp.json in the repo, and the server is listed in the MCP Registry as io.github.cyberchronos00/cg-code-graph.
Run
cg-mcp --db out/graph.db --gates examples/bookstore.gates.json --plans examples/plans
--db defaults to out/graph.db. --root is the project root for a single-repo DB (the index tool). --plans defaults to plans.dir in .cg.yaml, else <repo>/plans. Flags: cg-mcp -h.
One cg-mcp process can serve many graphs. Repeat --db (NAME=PATH or a path), pass --db-dir (every *.db in that directory), or point at a registry file. The file is ~/.config/cg/graphs.toml unless CG_GRAPHS or --graphs sets another path. Each entry has a name, a database path and one or more repo roots, so a combined graph (cg link) is one entry. With no --db on the command line, a registry file at the default path is loaded on its own. A single --db stays the one-graph server: replies have no graph field and tools do not gain a graph argument.
default = "shop"
[[graphs]]
name = "shop"
db = "/abs/shop.db"
roots = ["/abs/shop"]
[[graphs]]
name = "workspace"
db = "/abs/workspace.db"
roots = ["/abs/web", "/abs/api"]
The graphs tool lists name, roots, languages, indexed commit and freshness (fresh, stale or unknown). It is part of --tools core. Every other tool takes an optional graph name. Omit it and the server resolves, in order: a path in the call, then MCP roots (roots/list) or the working directory, then the configured default. When several graphs match and one graph's roots contain the others, that graph is selected. Otherwise, if exactly one matching graph's roots are only the repo that contains the path (its own graph, not a combined graph that also includes that repo), that graph is selected. Otherwise the configured default is used when it is one of the matches. If none of those applies, the reply lists the choices and does not guess. From a shared backend directory, with backend, appA+backend and appB+backend registered, the server selects backend. Replies from a multi-graph server start with graph <name> and the structured result includes graph. Each call opens only the database it resolved, so registered graphs that nobody queries are not opened.
A registry entry may set tools (written by cg install --migrate from that host's --tools or CG_MCP_TOOLS). The process still lists one tool set: the server's --tools when it is set, otherwise the union of those allowlists. A graph with no tools value keeps every listed tool. A call that resolves to a graph whose allowlist omits the tool returns tool <name> is not allowed for graph <name> and does not run. One graph's shorter allowlist does not remove tools from the other graphs.
cg install --global --registry writes one cg entry with no --db, which reads that registry. cg install --migrate prints how existing per-repo cg-* entries would fold into the registry; --apply writes the registry and the host config and keeps a .bak of each file it changes.
Tools
| tool | what it returns |
|---|---|
explore | start here for a broad question: matching symbols' source, entry points, call paths and blast radius, within a token budget |
reaches | what depends on the targets, grouped by entry classification; connection keeps one database connection and an edge on another one shows conn=<name> (connections) |
impact | callers of a method up to entry points; overrides listed apart from callers; a connections: line when the method uses a non-default database connection |
callers | direct callers; ref@file:line when code takes the function as a value |
siblings | hierarchy, the same method on sibling classes, shared resources |
downstream | forward dependencies (calls, HTTP, routes, tables) |
path | one shortest evidence chain between two specs |
node | one node and its edges; an ambiguous name lists candidates |
snippet | that symbol's source: path:start-end, then numbered lines |
search | name / FQN substring, plus route guard and auth names |
routes | routes with guards; filter by writes, reaches, missing or unguarded (routes and guards) |
writers / readers | writers or readers of a table, a column (table.column, column:table.column) or a stored property (Type.prop, Class::$prop); the same specs, groups and errors as cg writers / cg readers (spec forms); connection keeps one database connection |
roundtrip | heuristic: a lossy write of Type.prop read back into UI state |
lint_async_state | heuristic async-state lints |
api_calls | client HTTP calls, request keys and the matched route. route:VERB /path matches that route only (exact; * or route:* widens). An unknown route returns no such route and the nearest routes |
channels | broadcast channels (Channels and tests) |
bridges | web / native bridge endpoints (Web / native bridges) |
protocol_links | protocol endpoints and their checks (Protocol links) |
llm_tools | LLM and MCP tools, resources and prompts (AI tools) |
external_systems | databases, caches, brokers and other hosts (External systems) |
attack_surface | findings and the inbound / outbound surface; same filters as cg surface (Attack surface) |
tests_covering | tests that reach a symbol, route or table |
affected | tests and entry points reached by changed files or a git diff (CLI reference) |
resolutions | where a concept's value is decided (Value facts) |
plan_list plan_load plan_validate plan_baseline | plan files under the plans directory |
plan_check | compact plan report; details=true adds file:line and chains |
index | re-index into temp files, then swap the DBs; .cg.yaml is re-read |
doctor | the cg doctor report; no graph needed |
coverage | languages, files, blind spots (Completeness) |
platforms / platform_divergence | build targets, and variants that leave one uncovered |
starters | starter queries that resolve to nodes in this graph |
stats | node and edge counts |
graphs | registered graphs: name, roots, languages, indexed commit, freshness |
Argument lists match the CLI (cg <cmd> -h). reaches, impact, downstream, path, routes and search take platform (windows, linux, macos, ios, android, web). Rust and C / C++ specs: Rust, C and C++ query specs. On a combined graph, repo:Class.method selects one repo (workspace linking). index with no arguments re-indexes every linked repo (two or more) and rebuilds the one combined link; repo or root narrows it. A root that matches nothing, an unknown repo, or a result with 0 nodes is refused and the current graph stays. explore ends with the fine-grained calls to make next (CLI reference).
Paths in replies are repo-relative. An empty result says why and suggests the next call.
Coverage
coverage (and cg coverage) reports parser mode, file counts and blind spots. Three consequences for other tools:
routes,impact,callers,reaches,tests_coveringandplan_checkadd onecoverage note:when a blind spot could affect that answer.- An empty reply or an unknown symbol ends with a coverage line for the whole index.
- Every reply includes a
completenessobject (complete, per-language counts,blind_spots) so the agent can fall back to search without parsing the prose.
Where the answer is not complete, use normal search and file reading for that part. Sample replies: MCP sample outputs.
Staleness
When the working tree has changed since this graph was built, tool replies add one index note: line and the structured content includes stale: true. The check compares <db>.refresh.state (the fingerprint of paths, mtimes and sizes that cg refresh stored, plus the database mtime) with the current tree. File contents are not hashed. A database built by cg index or the index tool has no state file, so any listed file newer than the database counts. Paths come from one git ls-files call. The result is cached for 5 seconds. index and doctor skip the check. A combined graph (cg link) is not checked. CG_NO_STALE_CHECK=1 turns the check off (Configuration).
Install into a host
cg install registers this server under the key cg. cg uninstall removes that entry. Both preview a unified diff and ask before writing (--yes skips the prompt, --dry-run writes nothing). A second install with the same entry prints no change (cg already registered). The edit affects only the cg member, and install then uninstall leaves the file byte-identical.
Two known edges: a file that was already {} or {"mcpServers": {}} is deleted or collapsed by uninstall, and a TOML file without a final newline gains one.
The command is the absolute cg-mcp next to the Python that is running cg (so a venv, pipx or uv tool wins over an older cg-mcp on PATH), and --db is the absolute graph path. Project scope also passes --root. --portable writes the relative entry below instead, for a config committed to git. --global without --db exits 2: a global entry serves one graph. --global --registry is the exception: one cg entry and no --db, for a server that reads the graph registry. --migrate (dry run; --apply writes) folds per-repo cg-* entries into that registry.
{"mcpServers": {"cg": {
"command": "cg-mcp",
"args": ["--db", "out/graph.db"]}}}
Add --gates and --plans on the manual command when you use them. An older code-graph key whose command is cg-mcp is reported at install time; cg uninstall removes it. A code-graph entry that is not cg is left alone.
Codex reads <root>/.codex/config.toml only in trusted projects. cg doctor lists every host where cg (or a cg-owned code-graph key) is registered, and says when the command or the database is missing.
--host | project (--project, default) | global (--global) | docs |
|---|---|---|---|
cursor | <root>/.cursor/mcp.json | ~/.cursor/mcp.json | Cursor |
claude | <root>/.mcp.json | claude mcp add-json (user scope; claude must be on PATH) | Claude Code |
claude-desktop | global only | macOS ~/Library/Application Support/Claude/claude_desktop_config.json; Windows %APPDATA%\Claude\claude_desktop_config.json; no Linux build | Claude Desktop |
vscode | <root>/.vscode/mcp.json (servers) | $COPILOT_HOME/mcp-config.json, else ~/.copilot/mcp-config.json | VS Code |
windsurf | global only | $XDG_CONFIG_HOME/devin/mcp_config.json (else ~/.config/devin/…); Windows %APPDATA%\devin\mcp_config.json | Windsurf |
codex | <root>/.codex/config.toml | ~/.codex/config.toml | Codex |
gemini | <root>/.gemini/settings.json | ~/.gemini/settings.json | Gemini CLI |
zed | <root>/.zed/settings.json | ~/.config/zed/settings.json (macOS and Linux) | Zed |
Without --host, cg acts on hosts it can see (the config file or its directory, or claude on PATH for Claude Code's user scope) and prints detected: …. Flags and exit codes: install.
Agent rules: project-scope cg install also writes a short "use cg first" rule (use search, impact and snippet before grep or opening whole files, graphs when unsure which graph) to two places: a <!-- cg:start --> … <!-- cg:end --> section in AGENTS.md, which Claude Code, Codex and other agents read, and .cursor/rules/cg.mdc with alwaysApply: true. Both appear in the preview, and the output names each file written. A re-run replaces only the marked section, so it never duplicates the rule and keeps everything else in the file. An existing cg.mdc without front matter gets the alwaysApply header added once in front. cg uninstall removes the section and deletes a file that held nothing else. --no-rules skips both files. --global never writes them.
cg agents install writes a short cg reading-rules block into AGENTS.md, CLAUDE.md or .cursor/rules/cg.mdc. It previews the change and asks before writing. --mcp edits only the cg entry in .cursor/mcp.json (or --mcp-file). cg agents remove takes it back out. See cg agents -h.
Choosing tools
CG_MCP_TOOLS and cg-mcp --tools choose which tools are listed. Unset or empty means all of them. Items are separated by commas or spaces:
coreisexplore,search,node,snippet,impact,reaches,callers,routes,downstream,path,coverage,index,graphs- a name or an
fnmatchglob (plan_*) adds tools - a leading
-removes them (all,-plan_*)
An unknown name is ignored after one warning. If the selection is empty, cg-mcp exits 2. Codex enabled_tools and Gemini includeTools can filter again on the host side. cg install --tools LIST stores the same list in the host entry. Per-graph tools in the registry narrows calls for that graph only; the listed set stays the union (or this server-wide list, when it is set).
Server instructions
The initialize result carries short instructions (under 600 characters): use cg before grep or opening files, search to find a symbol, impact / reaches before editing, snippet instead of whole files, graphs when unsure, and normal search only for what cg reports as not covered. Each core tool's description opens with when to use it. The full reference (stacks, spec forms, per-feature notes) is the resource cg://guide.
Suggested agent instructions
Before changing code that affects a table, column, DB connection, config key or route, call the code-graph MCP tools:
- reaches(<target>) for every function and entry point that depends on it;
- impact(<Class::method>) before editing a method, and siblings(<Class::method>) for parallel paths;
- routes(writes="*", unguarded=true) or routes(reaches=[<target>]) for guards;
- for a planned feature, write plans/<name>.yaml, then plan_check(<name>) and resolve every MISSING FROM PLAN item.
Quote the file:line evidence. Re-run index after editing.
If coverage() or a coverage note says a language or file is not covered, use normal search for that part.
Known limitations
code-graph is beta. This page is the cross-cutting ceiling: how far an answer reaches when the graph is incomplete. Language gaps are on the language pages, linked at the end.
code-graph MCP: sample outputs (real calls via the MCP Python SDK stdio client)
Server: code-graph, DB: combined graph of examples/bookstore-api + examples/bookstore-web. Generated by tests/mcp_e2e.py. Volatile values (index timestamps and durations) are replaced by placeholders, so regenerating the file on unchanged code gives identical bytes.