Start

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

  1. 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.
  2. Add the server to your client. cg install --host cursor (or claude, 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

toolwhat it returns
explorestart here for a broad question: matching symbols' source, entry points, call paths and blast radius, within a token budget
reacheswhat depends on the targets, grouped by entry classification; connection keeps one database connection and an edge on another one shows conn=<name> (connections)
impactcallers of a method up to entry points; overrides listed apart from callers; a connections: line when the method uses a non-default database connection
callersdirect callers; ref@file:line when code takes the function as a value
siblingshierarchy, the same method on sibling classes, shared resources
downstreamforward dependencies (calls, HTTP, routes, tables)
pathone shortest evidence chain between two specs
nodeone node and its edges; an ambiguous name lists candidates
snippetthat symbol's source: path:start-end, then numbered lines
searchname / FQN substring, plus route guard and auth names
routesroutes with guards; filter by writes, reaches, missing or unguarded (routes and guards)
writers / readerswriters 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
roundtripheuristic: a lossy write of Type.prop read back into UI state
lint_async_stateheuristic async-state lints
api_callsclient 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
channelsbroadcast channels (Channels and tests)
bridgesweb / native bridge endpoints (Web / native bridges)
protocol_linksprotocol endpoints and their checks (Protocol links)
llm_toolsLLM and MCP tools, resources and prompts (AI tools)
external_systemsdatabases, caches, brokers and other hosts (External systems)
attack_surfacefindings and the inbound / outbound surface; same filters as cg surface (Attack surface)
tests_coveringtests that reach a symbol, route or table
affectedtests and entry points reached by changed files or a git diff (CLI reference)
resolutionswhere a concept's value is decided (Value facts)
plan_list plan_load plan_validate plan_baselineplan files under the plans directory
plan_checkcompact plan report; details=true adds file:line and chains
indexre-index into temp files, then swap the DBs; .cg.yaml is re-read
doctorthe cg doctor report; no graph needed
coveragelanguages, files, blind spots (Completeness)
platforms / platform_divergencebuild targets, and variants that leave one uncovered
startersstarter queries that resolve to nodes in this graph
statsnode and edge counts
graphsregistered 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_covering and plan_check add one coverage 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 completeness object (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.

--hostproject (--project, default)global (--global)docs
cursor<root>/.cursor/mcp.json~/.cursor/mcp.jsonCursor
claude<root>/.mcp.jsonclaude mcp add-json (user scope; claude must be on PATH)Claude Code
claude-desktopglobal onlymacOS ~/Library/Application Support/Claude/claude_desktop_config.json; Windows %APPDATA%\Claude\claude_desktop_config.json; no Linux buildClaude Desktop
vscode<root>/.vscode/mcp.json (servers)$COPILOT_HOME/mcp-config.json, else ~/.copilot/mcp-config.jsonVS Code
windsurfglobal only$XDG_CONFIG_HOME/devin/mcp_config.json (else ~/.config/devin/…); Windows %APPDATA%\devin\mcp_config.jsonWindsurf
codex<root>/.codex/config.toml~/.codex/config.tomlCodex
gemini<root>/.gemini/settings.json~/.gemini/settings.jsonGemini 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:

  • core is explore, search, node, snippet, impact, reaches, callers, routes, downstream, path, coverage, index, graphs
  • a name or an fnmatch glob (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.