@samchon/graph is an MCP server that gives AI agents a code graph instead of source files.
It indexes a codebase in 16 languages into a graph of declarations and their relationships, and answers an agent's code questions from that index through a single tool. Semantic edges come from each language's language server when one is installed; otherwise the separately packaged @samchon/graph-sitter best-effort fallback takes over.
Coding agents normally answer a code question by grepping the repository and reading file after file into context, and that reading is most of the token bill. The graph removes the need for it, and its own answers stay small in turn: they carry names, signatures, relationships, and source spans, never file bodies.
Since neither side of that exchange grows with the repository, the cost falls by about the same proportion in every situation, on every codebase — for an agent that trusts the graph result enough to stop there. codex/gpt-5.4-mini does; see the Benchmark section below for a harness where a model doesn't, and reads on top of the graph call anyway. That even distribution is what separates this from codegraph and serena when it holds, and it shows directly in the chart below:
npm install -D @samchon/graph{
"mcpServers": {
"samchon-graph": {
"command": "npx",
"args": ["-y", "@samchon/graph"]
}
}
}Start the client from the project root. The server builds one resident graph and answers every MCP call from memory. If no language server is installed, a built-in static indexer parses the source directly; every language indexes in about 1–2 seconds, even on large repositories.
A language server improves the graph with semantically resolved edges. Install the ones for your stack; nothing is provided automatically, and an installed editor such as VS Code does not expose a stdio language server:
| Language | Server | Install |
|---|---|---|
| TypeScript | ttscgraph / ttscserver |
npm i -D ttsc@^0.19.4 typescript |
| Python | pyright-langserver |
npm i -D pyright |
| Go | gopls |
go install golang.org/x/tools/gopls@latest |
| Rust | rust-analyzer |
rustup component add rust-analyzer |
| C / C++ | clangd |
LLVM release, or your package manager |
| Java | jdtls |
Eclipse JDT LS (needs JDK 21+) |
| C# | csharp-ls |
dotnet tool install -g csharp-ls |
| Kotlin | kotlin-language-server |
fwcd/kotlin-language-server |
| Swift | sourcekit-lsp |
ships with the Swift toolchain |
| Scala | metals |
cs install metals |
| Zig | zls |
zigtools/zls |
| Ruby | ruby-lsp |
gem install ruby-lsp |
| PHP | intelephense |
npm i -D intelephense |
| Lua | lua-language-server |
LuaLS/lua-language-server |
| Dart | dart |
ships with the Dart SDK |
Each server must be on PATH. If none is present for a file's language, that language falls back to the static indexer automatically.
TypeScript uses the compiler-owned ttscgraph snapshot first. The binary is resolved from the target project's ttsc installation; TTSC_GRAPH_BINARY can point to an exact absolute binary for development or release verification. If the binary is unavailable, its schema/provenance cannot be trusted, or the requested build is deliberately capped, indexing states the reason and falls back to ttscserver, then to the static indexer when no server is available. ttscgraph schema 5 is the complete contract; older schema 3 producers are refused because their source manifests cannot prove every declaration fact, then indexing falls back honestly to ttscserver.
JavaScript is intentionally not indexed. In an arbitrary repository, .js/.jsx/.mjs/.cjs files are as often build output or vendored bundles as handwritten source, and the graph cannot tell which without project-specific provenance.
Each repository is measured with headless agent runs per arm (baseline with no MCP, @samchon/graph, codegraph, codebase-memory, serena) on two prompt families, across two agent CLIs (codex and Claude Code). The corpus pins 13 repositories, one per language represented in codegraph's own evaluation suite and runnable with a full language-server index on the benchmark host.
Every repository is asked the same onboarding question. Every arm that mounts a tool receives the same tool-neutral nudge; the baseline receives only the same checkout-grounding rule used by the reference harness.
I'm new to this codebase and need a real code-based tour before my first behavior change.
Find the central runtime flow, trace it from the public API to the code that does the work, and show the nearby code paths and tests I should read next.
codegraph's own per-repository questions, verbatim:
| Project | Language | Prompt |
|---|---|---|
| excalidraw | TypeScript | How does Excalidraw render and update canvas elements? |
| gin | Go | How does gin route requests through its middleware chain? |
| flask | Python | How does Flask dispatch a request to a view function? |
| tokio | Rust | How does tokio schedule and run async tasks on its runtime? |
| gson | Java | How does Gson serialize an object to JSON? |
| redis | C | How does Redis parse and dispatch a client command? |
| leveldb | C++ | How does LevelDB read and write a key through its storage engine? |
| sinatra | Ruby | How does Sinatra match a request to a route handler? |
| slim | PHP | How does Slim handle a request through its middleware? |
| serilog | C# | How does Serilog route a log event to its sinks? |
| koin | Kotlin | How does Koin resolve and inject dependencies? |
| lualine | Lua | How does lualine assemble and render its statusline sections and components? |
| darthttp | Dart | How does the http package send a request and produce a response? |
| Project | Language | First index |
|---|---|---|
| slim | PHP | 5s |
| excalidraw | TypeScript | 5s |
| gin | Go | 15s |
| leveldb | C++ | 16s |
| darthttp | Dart | 23s |
| lualine | Lua | 25s |
| flask | Python | 55s |
| gson | Java | 2m22s |
| sinatra | Ruby | 2m35s |
| redis | C | 2m50s |
| tokio | Rust | 3m |
| koin | Kotlin | 19m25s |
| serilog | C# | not recorded |
One-time cost per repository. The server re-scans only changed files after that (see How it works); later calls are free.
kotlin-language-server, jdtls, and csharp-ls are particularly slow: each resolves the whole project before answering anything.
TypeScript already closes that gap through the compiler-owned ttscgraph snapshot. The remaining languages use their listed language servers until their compiler-owned bulk providers land.
Running the suite spends real API credits, so it is never wired into CI:
git clone https://github.com/samchon/graph
cd graph
pnpm install
pnpm --filter @samchon/graph-benchmark test # hashes + trace audit + deterministic SVG/PNG
pnpm --filter @samchon/graph-benchmark corpus # 13 repos / 13 languages, commit-pinned
pnpm --filter @samchon/graph-benchmark preflight # zero-spend go/no-go
pnpm --filter @samchon/graph-benchmark suite -- --arm=baseline --runs=5 --harness=codex
pnpm --filter @samchon/graph-benchmark suite -- --arm=graph --runs=1 --harness=codex
pnpm --filter @samchon/graph-benchmark orchestrate -- --all --arm=baseline --tools=baseline --prompt-families=all --models=gpt-5.4-mini --runs=1
pnpm --filter @samchon/graph-benchmark orchestrate -- --all --arm=graph --tools=all --prompt-families=all --models=gpt-5.4-mini --runs=1
pnpm --filter @samchon/graph-benchmark audit -- --report=<report.json>
pnpm --filter @samchon/graph-benchmark publish -- --from=<suite-output-directory>
pnpm --filter @samchon/graph-benchmark render:png # reference SVG + exact 2x PNG/**
* ## Code Graph MCP
*
* `inspect_code_graph` returns an index-built __LANG__ graph contract for the
* current on-disk source snapshot.
*
* Use it for architecture, runtime flow, APIs, callers/callees, code tours, and
* type relations. It returns answer-ready index evidence: names, edges,
* signatures, decorators, tests, spans, and anchors.
*
* Every returned fact — each name, edge, signature, and span — is checked
* against the index for the snapshot that call synchronized, so trust it
* without re-checking against files. Where an operation ranks a shortlist
* against your question (`lookup`, `entrypoints`, `tour`), the facts stay
* checked but the selection is heuristic: judge whether its coverage answers
* you, and a follow-up request or a read of a cited span is fair when it does
* not.
*
* ## Requests
*
* A request is a union: pick the single type below that best fits the question,
* and submit exactly that one.
*
* - `tour`: architecture, runtime flow, orientation, or a code tour. One call is
* the whole answer; do not split it. Name the machinery you expect it to be
* made of in its `reinterpretations`, or send none.
* - `entrypoints`: find where execution starts when entry points are unknown.
* - `lookup`: locate a named symbol.
* - `trace`: follow calls or data flow forward or backward from a symbol, or —
* with `to` — the path between two symbols when both ends are known, which is
* the one call that answers "how does A reach B".
* - `details`: signatures, members, and relations of named symbols — including
* the classes that implement an interface, which is the one call that answers
* "what actually implements this".
* - `overview`: project layers and folder structure.
* - `escape`: the answer is outside the graph (source body text, files outside
* the indexed languages, exact search).
*
* ## Chain of Thought
*
* Fill these fields in order before the call; each one narrows the reasoning
* toward the single request you submit.
*
* - `question`: the code question, in the user's own words.
* - `draft`: `{ reason, type }` — why the smallest request that could answer it,
* then that request's `type`.
* - `review`: fix a broad, stale, or duplicate draft. If the graph already
* answered, or the evidence is outside it, escape.
* - `request`: the final choice. Each branch documents its own fields; fill them
* from what the branch says, not from what another branch wanted.
*
* ## What to trust
*
* Before source edits, every returned fact has been checked against the index
* named by `audit`. Never use extra graph calls, repository search, or file
* reads to doubt, fact-check, re-derive, re-narrate, or re-confirm a returned
* node, span, edge, signature, decorator, test, reference, step, or anchor. The
* server checked each one against the current index for the snapshot the call
* synced to.
*
* Selection is separate. `lookup`, `entrypoints`, and `tour` match your
* question and return a scored, ranked, per-file-capped, limited shortlist;
* their facts stay checked, but whether the shortlist covers what you asked is
* yours to judge, and their `audit` says that instead of claiming completeness.
* A follow-up request or a read of a cited span for missed coverage is
* legitimate — re-confirming a fact the graph already checked is not.
*
* ## Stop
*
* Let the result's `next` set the pace, and do not re-confirm what the graph
* already checked.
*
* - A span is a citation, not a cue to open the file to re-check a fact.
* - Follow the result's `next`: `answer` means stop and answer from it, `inspect`
* means make exactly the one request it names, `outside` means escape,
* `clarify` means restate the request.
* - For a ranked shortlist (`lookup`, `entrypoints`, `tour`), `next` and
* `truncated` say whether coverage is settled; when it is not, one more
* request is the right move — not a file read to re-verify facts already
* given.
*/
export interface ISamchonGraphApplication {
/**
* Answer a __LANG__ question from this repository's own program index.
*
* The graph holds every symbol, call, type, decorator and test, each with its
* file and line, resolved from the source on disk now. Submit exactly one
* request:
*
* - `tour`: architecture, the runtime flow from the public API to the code that
* does the work, nearby paths, and the tests to read — a whole orientation
* in one call
* - `trace`: what a symbol calls, what calls it, or the path from A to B
* - `details`: signatures, members, and what implements an interface
* - `lookup`: where a named symbol is declared
* - `entrypoints`: where execution starts, when the entry is unknown
* - `overview`: the project's layers and folder structure
*
* Every fact in a result is checked against the index before return, so no
* fact needs verifying; for the ranked operations (`lookup`, `entrypoints`,
* `tour`), judge whether the shortlist covers your question. Read a file for
* what the graph does not carry: a body or the text inside a span.
*
* @param props Reasoning plus one graph request
* @returns Matching `result` union member
*/
inspect_code_graph(
props: ISamchonGraphApplication.IProps,
): Promise<ISamchonGraphApplication.IOutput>;
}
export namespace ISamchonGraphApplication {
/** Draft, review, then submit exactly one graph request or escape. */
export interface IProps {
/**
* The code question, in the user's own words.
*
* Cut a long message down to the sentences that state the ask, but keep
* their terms: the graph ranks against these words, so a rewrite ranks a
* different answer.
*/
question: string;
/** The smallest request that could answer, and why. */
draft: IDraft;
/**
* Correct the draft. Escape if the graph already answered, or the next
* evidence is outside the graph.
*/
review: string;
/** Final graph request chosen after review, or a no-op escape. */
request:
| ISamchonGraphEntrypoints.IRequest
| ISamchonGraphLookup.IRequest
| ISamchonGraphTrace.IRequest
| ISamchonGraphDetails.IRequest
| ISamchonGraphOverview.IRequest
| ISamchonGraphTour.IRequest
| ISamchonGraphEscape.IRequest;
}
/** First-pass plan; `reason` precedes `type` so it is written first. */
export interface IDraft {
/** Why this is the smallest useful next step. */
reason: string;
/** The request type being considered. */
type: IProps["request"]["type"];
}
/** The selected request's output. `result.type` mirrors `request.type`. */
export interface IOutput {
/**
* What the server checked this result against before returning it, in its
* own words. The audit names the LSP, static, or hybrid index that built the
* current snapshot.
*
* The audit is operation-aware. For the walks from a named handle (`trace`,
* `overview`) it reports the structure held for the named handles, bounded
* where `truncated` says. For `details` it reports the two halves of a
* resolved symbol: its own shape returned whole unless the caller explicitly
* capped members, and its fan-out returned as a slice with `trace` for the
* rest. For ranked operations (`lookup`,
* `entrypoints`, `tour`) it additionally says that selection was matched,
* scored, ranked, and limited against the question, so the facts are checked
* but shortlist coverage is yours to judge.
*/
audit: string;
/** What to do with `result`: answer, inspect one named request, or escape. */
next: ISamchonGraphNext;
/** Result branch matching the submitted `request.type`. */
result:
| ISamchonGraphEntrypoints
| ISamchonGraphLookup
| ISamchonGraphTrace
| ISamchonGraphDetails
| ISamchonGraphOverview
| ISamchonGraphTour
| ISamchonGraphEscape;
}
}question, draft, and review are required fields, so the model writes its reasoning into the call itself: state the question, draft the smallest request, then review the draft. A prompt line can be ignored; a required field cannot.
The review is allowed to overturn the draft, and that matters more than the planning. When an agent like Claude Code enters the tool with a question the graph cannot answer, review replaces the drafted request on the spot, and escape backs out entirely. A wrong entry costs one small call instead of a derailed session.
question is asked once, and the tour ranks against it. Its JSDoc says so, because by the time the string arrives it is whatever the model chose to write, and the schema is the only text the model reads before it fills the field: "Cut a long message down to the sentences that state the ask, but keep their terms: the graph ranks against these words, so a rewrite ranks a different answer."
Nothing is forbidden. The tool description says when the graph applies and when to stop. Grep and file reads stay available, and the agent still uses them when they are the right move.
What keeps the agent on the graph is precision. Answers carry names, signatures, edges, and spans resolved by a language server, so the agent accepts them as final instead of re-verifying with its own reads. And since no file body is ever included, a large repository cannot inflate the response.
serena and codegraph fight the agent instead:
- dozens of tools around one graph, so the agent often picks the wrong entry point
- 100–150 lines of injected instructions, spent mostly on forbidding grep and file reads
- source snippets inlined into answers, which reintroduces the reading cost a graph exists to remove
- loosely structured answers the agent does not trust, so it goes back to reading the files to verify them
- no way to back out, so a wrong entry keeps paying tool calls instead of escaping
Here the same policy fits in one typed contract, enforced by schema instead of pleaded for in prose.
Thanks for your support.
Your donation encourages @samchon/graph development.
- Motivation: real-world use of
codegraphthat raised token cost instead of lowering it and visibly degraded agent reasoning. - Predecessor:
@ttsc/graph, the TypeScript-only original that this project generalizes; its launch post analyzes why earlier graph tools did not reduce the token bill. - Function calling harness: part 1 — validation feedback and part 2 — CoT compliance, the typia technique the contract is built on.
- Compared against:
codegraphandserena. - Protocol: the Model Context Protocol and the Language Server Protocol.
- Validation & MCP surface:
typiaand@typia/mcp.
