CoreGraph can be driven three ways besides the CLI:
- MCP — let an LLM agent (Claude Code, Claude Desktop) call the graph as tools.
- LSP — wire go-to-definition / find-references into an editor.
- HTTP — query the daemon over a local JSON API.
The MCP and LSP bridges route through the daemon's cached graph, auto-spawning the
daemon when it is not running; they fall back to a one-shot in-process build only
when auto-start is suppressed (--no-auto-start / COREGRAPH_NO_AUTO_START) or
when spawning/IPC fails. The HTTP bridge does not share that cached graph:
coregraph server start --http builds a separate graph copy once at daemon
startup that is never refreshed by the file watcher, healing, or reindex — only
the IPC-served graph is kept up to date.
Start a stdio MCP server with:
coregraph mcpIt speaks JSON-RPC and answers initialize, tools/list, and tools/call.
Register CoreGraph in your MCP client. For Claude Code, add it to .mcp.json
at the project root; for Claude Desktop, use claude_desktop_config.json. The
shape is the same:
{
"mcpServers": {
"coregraph": {
"command": "coregraph",
"args": ["mcp"]
}
}
}The server runs in the client's working directory, which CoreGraph treats as the project to index.
There are exactly five tools. Names are plain (no prefix):
| Tool | Input | Returns |
|---|---|---|
query |
{ "name": string } (required) |
Symbols matching name across the project |
impact |
{ "name": string (required), "transitive": bool = false, "depth": integer = 5 } |
Impact analysis — always traverses the impact closure up to depth (default 5); transitive only sets the transitive label in the output. For direct dependents only, pass depth: 1 |
orphans |
{} |
Dead-code candidates: code symbols with no incoming or outgoing edges |
inconsistencies |
{} |
Cross-file inconsistencies: enum / api-path / config-key (doc-drift is CLI-only) |
stats |
{} |
Graph summary: symbol count and edge count |
Note: the MCP impact tool takes depth (not max-depth — that rename applies
only to the CLI impact command).
Example tools/call for impact:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "impact",
"arguments": { "name": "build_router", "depth": 3 }
}
}Start a stdio LSP bridge with:
coregraph lspPoint your editor's LSP client at that command for the workspace. The server advertises three capabilities:
| Capability | LSP request | What it does |
|---|---|---|
definitionProvider |
textDocument/definition |
Jump to a symbol's definition |
referencesProvider |
textDocument/references |
List all references to a symbol |
workspaceSymbolProvider |
workspace/symbol |
Search symbols by name across the project |
There is no hover provider — definition, references, and workspace-symbol search are the full LSP surface.
Run the daemon with an HTTP listener:
coregraph server start --httpWith no address, --http binds 127.0.0.1:27787 (deliberately off the
common 8080 / 8000 / 3000 ports). Pass an explicit address to override:
coregraph server start --http 127.0.0.1:9000By default the listener is localhost-only, and that default is enforced. To bind a
non-localhost address (for example to reach the daemon from another machine), add
--allow-external. Note that --allow-external currently takes effect only with
coregraph server start --foreground: in the default detached mode (and with
server restart) only --http is forwarded to the background daemon, so the flag
is dropped, the child fails the external-bind gate, and you see only a generic
"daemon failed to start within timeout" error.
There is no SSE or websocket stream — the API is request/response only.
| Method | Route | Params / body | Returns |
|---|---|---|---|
| GET | /health |
— | { status, version, symbol_count } |
| POST | /query |
{ name, limit = 50 } |
{ name, count, symbols[] } |
| POST | /batch |
{ queries: [name, …] } |
{ results: [QueryResponse, …] } |
| GET | /api/query |
?symbol=&page=0&page_size=50&budget=8000 |
{ query, matches[], pagination, budget } |
| GET | /api/expand |
?node=<id>&budget=2000 |
{ node, incoming[], outgoing[], budget } |
| GET | /api/impact |
?symbol=&depth=5 |
{ symbol, depth, reachable_count, edge_count, nodes[] } |
| GET | /api/source |
?file=&line=1&context=5 |
{ file, target_line, context_lines, total_lines, snippet[] } |
The HTTP /api/impact route takes depth (the CLI --max-depth rename does not
apply here).
Health check:
curl http://127.0.0.1:27787/healthLook up a symbol by name (POST body):
curl -X POST http://127.0.0.1:27787/query \
-H 'content-type: application/json' \
-d '{"name": "compute_impact", "limit": 50}'Impact analysis (GET query string):
curl 'http://127.0.0.1:27787/api/impact?symbol=build_router&depth=3'Each symbol in an HTTP /api/query matches[] array carries id, name,
kind, file, span_start, span_end — no confidence/trust fields. The
confidence/trust fields (confidence, trust, origin, trust_model,
stale_evidence_count, current_confidence) appear only on edge endpoints —
/api/expand's incoming[] / outgoing[] arrays — and in the CLI
--output-format json edge shape. Note that POST /query returns only
symbols[] as an array of name strings.
The same confidence/trust fields appear in the CLI's --output-format json
output, where an edge looks like this (CLI shape; the CLI labels endpoints with
direction, other_id, and other_name):
{
"direction": "incoming",
"kind": "calls",
"depth": 1,
"other_id": 40,
"other_name": "run",
"confidence": 0.8549999594688416,
"trust": "NameResolved",
"origin": "NameResolved",
"trust_model": "SourceEvidenced",
"stale_evidence_count": 0,
"current_confidence": 0.95
}confidence is the static base(kind) × base(origin) value (stored, clamped to
[0, 1]). current_confidence is base(origin) × 0.7^stale_evidence_count — the
stale-evidence decay applied to the origin base alone, omitting the kind factor
entirely. At zero staleness current_confidence equals the origin base, which is
≥ the stored confidence (equal only when the kind base is 1.0). See
confidence.md for how the two relate.