okf-rs

August 18, 2026 · View on GitHub

okf-rs

CI Release License: MIT OR Apache-2.0 OKF BundleDex

A fast, open-source Rust CLI that turns a codebase into a portable Open Knowledge Format (OKF) knowledge base — plain markdown files with YAML frontmatter, cross-linked into a real call graph, readable by humans and AI coding agents alike.

$ okf-rs generate .
Generated 146 concepts into knowledge
  Module       16
  Struct       18
  Enum         6
  Function     87
  Method       19

$ okf-rs validate
knowledge — no issues found

Why okf-rs?

Most codebase-analysis tools produce a proprietary graph database, an AI-specific context blob, or a pile of Markdown summaries you can't query or diff. okf-rs instead emits a conformant OKF bundle: ordinary .md files with YAML frontmatter, cross-linked by ordinary markdown links, that live in your repo like any other file.

That's not just a format preference — it's the actual safety property that matters for a knowledge layer AI agents act on. Every other approach in this space (a local SQLite index, a vector store, a proprietary graph) is only auditable by someone already running that tool. An OKF bundle isn't: it's git diff-able like any other file, so when the analyzer resolves a call wrong, the wrong edge shows up as a red line in a pull request, visible to a reviewer who has never run okf-rs and never will —

 # Calls

-- [decode_jwt](../../functions/auth/decode_jwt.md)
+- [decode_jwt_v2](../../functions/auth/decode_jwt_v2.md)

— not buried in a binary index file nobody opens. That's what "being wrong in public" as a design property buys you: bad extraction gets caught the same way a bad line of source code does, by the humans already reviewing the diff, not by whoever happens to go looking for it later.

  • Git-native — no proprietary runtime, database, or SDK required to read, write, review, or audit it; any tool that can read a markdown file can read the whole knowledge graph.
  • Fast — a native Rust core using tree-sitter for parsing, with per-file extraction parallelized across a rayon thread pool.
  • Deterministic — identical source always produces byte-identical output; no wall-clock timestamps, no unordered maps leaking into results (and generate --check-determinism verifies this directly, rather than asking you to take it on faith).
  • AI-ready — structured knowledge that doesn't require an LLM to produce, though one can optionally enrich it later.

See docs/specification.md for the full project specification, including how okf-rs compares to other tools in this space, docs/improvement-plan.md for a gap analysis against CodeGraph and code-review-graph — two SQLite-backed alternatives that converge on the same "pre-index once, query the index" thesis but implement it as a database plus a bespoke query layer rather than the artifact itself being the knowledge base — plus a prioritized improvement plan, docs/improvement-plan-provenance-diff.md for the layer built on top of that — resolver-version tracking, provenance-aware graph diffing, a diff --ci policy, artifact-level reproducibility metadata, and a specialized-vs-consolidated MCP tool-selection benchmark, shipped in full — benchmarks/ for a discoverable index of every reproducible benchmark this project ships (how to run each one, and the real results collected so far) — and ROADMAP.md for what's shipped and what's next.

Features

  • Repository scanning — recursive, .gitignore-aware, with git-aware indexing and manifest detection (Cargo.toml, package.json, pyproject.toml, go.mod); a Cargo/npm/monorepo workspace with several member packages is aggregated into one bundle, with one Package concept per member correctly linked to its modules
  • Semantic extraction for Rust, Python, TypeScript, JavaScript, Go, Java, C#, PHP, Kotlin, C/C++, and Swift — packages, modules, types (structs/classes/enums/interfaces/traits), functions, and methods, including public/private API-surface detection tailored to each language's actual visibility rules (explicit-opt-in for Rust/Java/C#/Swift, opt-out-by-default for PHP/Kotlin, section-based for C++, capitalization for Go)
  • Relationship extraction — imports, and a resolved call graph covering bare calls, member/self/this calls, static/scoped calls, and qualified module calls across all eleven languages
  • LSP-backed disambiguation (optional) — okf-rs generate --lsp resolves calls whose name is ambiguous project-wide by asking the project's real language server (textDocument/definition), on top of Tree-sitter's own unambiguous-name-only resolution; verified end to end against real rust-analyzer/pyright servers, with a timeout on unresponsive servers, Windows-aware executable lookup, and percent-encoded file:// URIs so paths with spaces or non-ASCII characters resolve correctly
  • OKF bundle generation — markdown + YAML frontmatter, cross-linked, with index.md navigation at every level; targets OKF v0.2, declaring okf_version: "0.2" in the bundle root and filling in each concept's generated.by trust field
  • Incremental indexingokf-rs generate caches each file's extraction by content hash, so a re-run only re-parses what actually changed
  • Watch modeokf-rs watch keeps a project's bundle up to date as files change, reusing the same incremental cache
  • Validation — frontmatter/schema checks (including the v0.2 generated/verified/sources/status/stale_after trust families and the root okf_version declaration), dangling-link detection, redundant-link detection, orphan detection, duplicate-identity checks (both path collisions and same-symbol-different-file), relationship-target resolution, and bundle-wide Calls/CalledBy symmetry checking
  • Search — ranked free-text search by symbol, package, module, type, and tag; optional embedding-based semantic search (okf-rs search --semantic) via any OpenAI-compatible /embeddings endpoint, ranking by meaning rather than wording
  • Graph queries — callers/callees, call-graph cycle detection, isolated-concept detection, cross-module dependencies, shortest call path, topology statistics (per-kind counts, relationship edge counts, connected components), and modularity-based package community detection (okf-rs graph communities) — a finer-grained signal than plain connected-component domains, since it tells two densely-connected clusters apart even when a single edge technically connects them
  • One-call contextokf-rs explore <query> (and the explore MCP tool) bundles a concept's signature, description, callers, callees, and blast radius into one response, instead of composing several separate search/graph calls
  • Coverage metricsokf-rs coverage reports description/tag completeness and call-graph participation across the bundle
  • Bundle diffing — compare a project's concepts between two git refs without touching your working tree
  • Change-impact analysisokf-rs impact <ref-a> <ref-b> extends diffing with blast radius (every concept transitively affected by a change), public-API membership, and call-cycle membership — a deterministic, structural risk signal, not an AI judgment call
  • PR review automationokf-rs review <ref-a> <ref-b> renders the same impact analysis as a Markdown, sticky-comment-ready report (with optional --fail-on-risk merge gating); see .github/workflows/pr-review.yml for a ready-to-use GitHub Action that posts and updates it on every PR push
  • Documentation generationokf-rs docs renders a bundle into a browsable static HTML site, a single consolidated Markdown document, a single paginated PDF, a GraphML graph (for Gephi/yEd/etc.), or an Obsidian vault, templated directly from the concept data — no LLM required
  • AI agent integrationokf-rs init writes/updates CLAUDE.md, AGENTS.md, and .github/copilot-instructions.md, idempotently; AGENTS.md holds the generated content and CLAUDE.md just imports it (@AGENTS.md), so tools that prefer AGENTS.md over CLAUDE.md (e.g. opencode) never end up reading stale or missing content; okf-mcp exposes search, explore, and graph queries as an MCP server for tools like Claude Code and opencode
  • Standalone binary — no runtime dependency beyond the OS's standard C library; see Packaging & Distribution

See ROADMAP.md for what's shipped (Phases 1-3 and both improvement plans complete) and what's next (Phase 4 — Ecosystem).

Installation

From a release binary

Prebuilt binaries for Linux (glibc + static musl), macOS (x86_64 + arm64), and Windows are attached to each GitHub Release. Download the archive for your platform, extract it, and put okf-rs on your PATH.

With Cargo

cargo install --git https://github.com/jyjeanne/okf-rs okf-cli

From source

git clone https://github.com/jyjeanne/okf-rs
cd okf-rs
cargo build --release
# binary at target/release/okf-rs

Quick start

$ okf-rs scan .
Project root: /path/to/your/project
Manifest: Cargo
1 source files:
  Rust         1

$ okf-rs generate .
Generated 6 concepts into knowledge
  Package      1
  Module       1
  Struct       1
  Function     1
  Method       2

$ okf-rs validate
knowledge no issues found

$ okf-rs search verify_token
 80  verify_token             Rust Method          functions/src/Auth/verify_token

okf-rs init records a project's default output directory in okf.toml, so later commands (generate, validate, search) don't need --output/--project repeated on every call.

What comes out

A generated concept file — knowledge/functions/src/Auth/verify_token.md from the example above — looks like this:

---
type: Rust Method
title: verify_token
resource: src/main.rs#L4-L6
generated:
  by: okf-rs/0.1.0
---

# Signature

`fn verify_token(&self, token: &str) -> bool`

# Calls

- [decode_jwt](../../../functions/src/Auth/decode_jwt.md)

Just a markdown file. Open it in any editor, render it on GitHub, or point an AI coding agent at the knowledge/ directory and let it follow the links.

Alongside the human-readable # Calls section above, the frontmatter also carries a machine-readable relationships field — each target with resolved_by (tree-sitter, or the language server that resolved it with --lsp, e.g. rust-analyzer) and confidence (exact/semantic), so a reviewer or a tool can tell how an edge was produced without leaving the file:

relationships:
  calls:
  - target: functions/src/Auth/decode_jwt
    resolved_by: tree-sitter
    confidence: exact

Tutorial: adding okf-rs to an existing codebase

The quick start above uses a toy example. This walks through adopting okf-rs in a real, already-existing project — install once, then wire it into how you and your AI coding agent actually work day to day.

1. Install the binary

Pick one (see Installation for details):

# Prebuilt binary — download from https://github.com/jyjeanne/okf-rs/releases,
# extract, and put `okf-rs` on your PATH, or:
cargo install --git https://github.com/jyjeanne/okf-rs okf-cli

2. Initialize it in your project

From your project's root:

cd /path/to/your-existing-project
okf-rs init .

This writes okf.toml (recording knowledge/ as the default bundle location, so later commands don't need --output/--project repeated), and creates or idempotently updates CLAUDE.md, AGENTS.md, and .github/copilot-instructions.md with a marked section pointing AI agents at the bundle — existing content in those files is preserved untouched. Skip the agent files with okf-rs init . --no-agent-files if you'd rather add that section yourself, or aren't using an AI agent.

3. Generate the bundle

okf-rs generate

This is safe to run on a large, real codebase: it's .gitignore-aware (never descends into node_modules, target, vendor, ...), aggregates a multi-package workspace/monorepo into one bundle automatically, and caches each file's extraction by content hash in .okf-cache.json — the first run parses everything, every run after that only re-parses files that actually changed. Add .okf-cache.json to .gitignore; it's a disposable local performance cache, not part of the bundle.

Decide whether knowledge/ itself belongs in git. Both are reasonable: committing it means the bundle reviews alongside the code that produced it (and is diffable, per PR, in git diff); .gitignore-ing it means treating it as a build artifact regenerated in CI. Either way, run okf-rs validate --ci in CI (see step 6) so a stale or broken bundle never ships silently.

Optional: fill in missing descriptions with an LLM

okf-rs generate --enrich --enrich-base-url http://localhost:11434/v1 --enrich-model llama3.1

Entirely optional, and never a hard dependency on one vendor: --enrich speaks the same chat/completions shape every OpenAI-compatible endpoint implements, so it works unmodified against Ollama, LM Studio, LocalAI, Crustly, or a cloud provider (pass --enrich-api-key, or set OKF_ENRICH_API_KEY) — a local server generally doesn't need one. Only concepts with no description are ever queried or overwritten: a hand-written one is left alone, and a previous --enrich run's output is reused straight from the bundle on disk rather than re-querying the endpoint on every generate.

Once descriptions exist, okf-rs suggest-links (same --enrich-* flags) looks for concepts that are semantically close by full-text search but have no relationship edge yet, and asks the endpoint whether each candidate looks like a genuinely missing link — advisory only, nothing is written back into the bundle:

okf-rs suggest-links --enrich-base-url http://localhost:11434/v1 --enrich-model llama3.1

Optional: bring in an existing DITA corpus

okf-rs generate --dita path/to/dita-topics/

A technical-writing team's existing DITA XML topics import as Document concepts, merged into the same bundle alongside everything extracted from source — so search, graph, and every other command work over docs and code together. A topic that fails to parse is skipped with a warning rather than failing the whole command. Going the other way, okf-rs docs --format dita exports a bundle (code, imported docs, or both) back out as a DITA topic set.

Optional: migrating an existing docs tree

If you're folding a pre-existing docs/-style tree straight into knowledge/ (rather than generating it from scratch), you'll likely have hand-maintained index.md files in subdirectories that carry their own YAML frontmatter. Spec §8 only allows frontmatter (an okf_version declaration) on the bundle-root index.md — every other index.md is a plain listing and must not have a frontmatter block at all. okf-rs validate catches this with an error naming the offending file; resolve it by either:

  • stripping the frontmatter, so the file becomes a plain §8 listing, or
  • renaming the file (e.g. index.mdoverview.md) so it's no longer treated as a directory index.

4. Explore it

okf-rs search verify_token                              # find a symbol by name
okf-rs search "parses a jwt" --ranked                    # ranked, relevance-scored full-text search (title/type/description/signature/tags)
okf-rs graph callers functions/src/auth/verify_token     # who calls it
okf-rs graph api                                         # the whole public API surface
okf-rs graph layers                                      # each package's layer in the dependency graph (0 = foundational)
okf-rs graph domains                                     # clusters of packages that depend on each other
okf-rs graph patterns                                    # Builder/Singleton/Factory/Visitor matches, by structural heuristic
okf-rs graph features                                    # REST endpoints, database models, event-flow participants, by naming heuristic
okf-rs docs --format html                                # a browsable static site, into docs/
okf-rs docs --format pdf                                 # a single paginated PDF, into docs.pdf
okf-rs docs --format dita                                # a DITA topic set + ditamap, into docs-dita/

Concept ids (like functions/src/auth/verify_token above) come from search's output — copy one from there rather than guessing the path convention.

5. Keep it current while you work

okf-rs watch

Regenerates once immediately, then again each time a burst of file changes settles (default 300ms debounce), reusing the same incremental cache generate does. Leave it running in a terminal alongside your editor; stop with Ctrl+C.

6. Wire it into CI

- name: Validate OKF bundle
  run: |
    okf-rs generate --no-cache
    okf-rs validate --ci

--ci treats orphaned-concept warnings as failures too, not just schema errors — useful once the bundle is something other tooling (docs, agents) actually depends on being correct. --no-cache in CI ensures a clean, from-scratch parse rather than trusting a cache that may not exist on that runner; drop it if you cache .okf-cache.json between CI runs and want the speed-up instead.

If you're also using --lsp, add okf-rs generate --lsp --check-determinism as its own CI step: it runs analysis twice independently and diffs the result, so a language-server-index-state difference between your machine and the CI runner shows up as a named, understood failure ("Non-deterministic: ...") instead of validate --ci reporting a confusing, unrelated-looking bundle mismatch. It writes nothing — safe to run before the real generate step above.

If you commit knowledge/ to the repo (so it's browsable on GitHub and by an agent without running okf-rs first — the git-native bet this project is built on), the recipe above has a gap: it regenerates the bundle fresh in CI every run, so it can never catch a contributor who changed source and forgot to regenerate-and-commit the bundle itself — CI just silently produces a correct bundle nobody sees, on top of the stale one actually sitting in the repo. Catch that instead:

- name: Check the committed bundle is up to date with source
  run: okf-rs generate --check-fresh
- name: Validate the committed bundle
  run: okf-rs validate --ci

--check-fresh re-analyzes the project, diffs that against the bundle already on disk (not against a second run — unlike --check-determinism, this is deliberately asymmetric), and fails with the exact stale files listed if they disagree; it never writes to knowledge/ itself, so there's nothing to accidentally commit back. Skip --enrich in this recipe — comparing against a freshly analyzed, unenriched bundle would flag every AI-written description as false staleness.

7. Register it with your AI coding agent

See MCP server below — this is the step that turns "an agent can technically read these files" into "an agent can query the graph directly," and is where most of the token savings in day-to-day agent use come from.

CLI reference

Usage: okf-rs <COMMAND>

Commands:
  init      Scan a project and write an `okf.toml` recording defaults for later commands
  scan      Recursively scan a repository and report what would be analyzed
  generate  Analyze a repository and write an OKF bundle
  watch     Watch a project and keep its OKF bundle up to date as files change
  validate  Validate that a directory is a conformant OKF bundle
  search    Search an OKF bundle by symbol, type, or tag
  explore   One-call context for a concept: signature, callers, callees, and blast radius
  coverage  Report content-completeness metrics: description/tag coverage and call-graph participation
  graph     Query the concept graph: callers, callees, cycles, isolated concepts, public API, communities, cross-module dependencies, and topology stats
  diff      Compare the OKF concepts between two git refs (added/removed/changed)
  impact    Change-impact ("blast radius") analysis between two git refs
  review    Render impact analysis as a Markdown, PR-comment-ready report
  docs      Generate human-readable documentation (HTML, Markdown, PDF, GraphML, or Obsidian) from an OKF bundle

Run okf-rs <command> --help for each command's options. okf-rs generate persists a .okf-cache.json at the project root keyed by each file's content hash, so a re-run only re-parses files that actually changed since the last one (report line: N files parsed, M reused from cache); pass --no-cache to bypass it and re-parse everything (the bundle it produces is byte-identical either way — the cache only affects how long it takes). Pass --lsp to also resolve calls whose name is ambiguous project-wide (more than one function/method sharing that name) by asking the call site's real language server (rust-analyzer for Rust, pyright-langserver for Python, wired up so far); this is strictly additive to Tree-sitter's own resolution and never changes the concept set, only adds Calls/CalledBy edges Tree-sitter's name-matching alone can't draw — it spawns and queries a real language server process, so it's meaningfully slower than a plain generate and skipped silently for a language with no available server. Pass --check-determinism instead of writing a bundle to verify determinism directly: it runs analysis twice, independently, and diffs the two renders byte-for-byte, reporting every differing file and exiting non-zero if they disagree — the tree-sitter-only path is expected to always pass; combined with --lsp it's the tool for catching the local-machine-vs-cold-CI-runner divergence a language server's own index state can introduce, before validate --ci reports a confusing bundle mismatch instead. Pass --check-fresh instead to check a different thing entirely: whether the bundle already sitting at --output still matches the current source, without touching it — diffs a fresh analysis against what's on disk (not against a second fresh run, unlike --check-determinism) and reports "Up to date" or lists exactly which files are stale; useful in CI for a project that commits knowledge/ to catch a contributor who changed source and forgot to regenerate. Both --check-* flags reject --enrich, since a live endpoint's response isn't reproducible enough to compare against either way. okf-rs watch runs that same cycle continuously: it regenerates once immediately, then again each time a burst of filesystem activity settles (--debounce-ms, default 300), printing a line only when something actually changed — silent on a wakeup caused by unrelated churn (e.g. a background cargo build touching target/) — and keeps running until interrupted (Ctrl+C). okf-rs graph has its own subcommands (callers, callees, cycles, isolated, api, modules, communities, stats, path, explain) — e.g. okf-rs graph callers functions/src/auth/verify_token lists everything that calls it, okf-rs graph cycles flags any call-graph cycles, okf-rs graph isolated lists concepts with no Calls/CalledBy edge at all (dead code or unresolved calls), okf-rs graph communities groups packages by modularity-optimization community detection (a finer signal than graph domains' plain connected components — two packages joined by only a thin, weak link can end up in different communities even though they're technically reachable from each other), and okf-rs graph stats reports per-kind concept counts, relationship edge counts by kind, and connected components of the call graph. okf-rs graph explain <from> <to> answers why a relationship exists, not just that it does: the relation kind plus a plain-English reason derived from that edge's provenance — "resolved via Tree-sitter's unambiguous, project-wide name match" for the default path, or "resolved by asking rust-analyzer which definition this call site actually resolves to" for one --lsp had to disambiguate — falling back to explaining the shortest call path hop-by-hop when there's no single direct relationship between the two. Like search and validate, graph reads a previously generated bundle rather than re-analyzing the project, so run okf-rs generate first (and again after source changes). okf-rs explore <query> (also an MCP tool) resolves query — an exact concept id, or free text ranked the same way search --ranked is — and returns its signature, description, direct callers/callees, blast radius, public-API membership, and cycle membership all in one response, instead of composing several separate search/graph calls. okf-rs search --semantic <query> ranks by embedding-cosine similarity instead of exact/ranked lexical matching, via --enrich-base-url/--enrich-model pointed at any OpenAI-compatible /embeddings endpoint (only concepts with a description are considered; run generate --enrich first if the bundle has none). okf-rs coverage reports what fraction of the bundle has a description, has at least one tag, and actually participates in the call graph — a quick signal for how filled-in the knowledge base is, distinct from validate's pass/fail checks. okf-rs diff <ref-a> <ref-b> compares two git refs' concepts without touching your working tree (it uses a temporary git worktree checkout for each ref); --ci classifies changes as source/resolver/confidence-level and prints the resolver-only rate (see below) alongside a nonzero RESOLVER CHANGES count. okf-rs diff-bundles <bundle-a> <bundle-b> is the same provenance-aware --ci-style report, but for two bundle directories already on disk instead of two git refs — there's no git ref to check out when the two bundles came from the same source snapshot analyzed under different conditions, most concretely two generate --lsp runs against two different installed resolver versions. Dogfooded against this repository's own source: comparing a rust-analyzer 1.90.0 bundle against a 1.94.1 bundle of the identical 1153-concept snapshot found 1292 resolver-only relationship changes against 2 genuine ones — a 99.8% resolver-only rate — but also caught that --lsp's own ambiguous-call resolution isn't perfectly deterministic run-to-run even holding the resolver version fixed (see docs/improvement-plan-provenance-diff.md's Phase G), so that 0.2% isn't safely attributable to the version bump itself without controlling for that noise. okf-rs impact <ref-a> <ref-b> extends that with change-impact analysis: for every added/removed/changed concept, its blast radius (every concept transitively affected, via Graph::transitive_callers), whether it's public API, and whether it sits in a call-graph cycle — a deterministic, structural risk signal rather than an AI judgment call. okf-rs review <ref-a> <ref-b> renders that same impact analysis as a Markdown report with a leading sticky-comment marker, ready to post as a pull-request comment (--fail-on-risk <N> additionally exits non-zero if any changed concept's blast radius reaches N, for CI merge gating); see .github/workflows/pr-review.yml for a ready-to-use GitHub Action wiring this into every PR. okf-rs docs reads a previously generated bundle (like search/validate/graph) and renders it for humans: --format html (the default) writes a browsable static site — one page per concept, cross-linked, plus a root index and one index per concept kind — into docs/; --format markdown writes a single consolidated docs.md with a table of contents instead; --format pdf writes a single paginated docs.pdf (grouped by kind, with a PDF outline/bookmark per concept for navigation) for printing, archiving, or sharing as one file; --format graphml writes a single docs.graphml graph for Gephi/yEd/any other GraphML-reading tool; --format obsidian writes one Markdown note per concept plus a root index, cross-linked with Obsidian's native [[wikilink]] syntax, into a directory openable directly as an Obsidian vault. All of these are templated directly from the bundle's concept data, no LLM involved. okf-rs init also writes/updates CLAUDE.md, AGENTS.md, and .github/copilot-instructions.md to point AI coding agents at the bundle — pass --no-agent-files to skip that.

Architecture

okf-rs is a Cargo workspace of small, single-purpose crates under crates/; okf-cli is a thin wrapper over the rest, so the same logic can be embedded by other Rust tools. See the Architecture section of the specification for the full crate-by-crate breakdown, including crates not yet built (okf-server — see ROADMAP.md).

MCP server

okf-mcp exposes a bundle's search, coverage, and graph queries as Model Context Protocol tools over stdio, so an MCP-aware agent can query the knowledge base directly instead of re-reading raw source: search, search_ranked, search_semantic, explore, coverage, and graph. graph is a single consolidated tool covering every graph-topology and architecture query — pass relation (callers, callees, path, explain, api, cycles, modules, isolated, stats, layers, domains, communities, patterns, or features) instead of calling one tool per relation. This replaced 13 separate graph_* tools (August 2026): each tool's JSON Schema sits in the system prompt for the whole session whether it's used or not, so a dozen-plus narrow tools cost real tokens on every turn just by being registered — see ROADMAP.md for the reasoning. explore is still the one to prefer when an agent needs more than one fact about the same concept (signature, callers, callees, blast radius) in one round trip; graph is for everything else that isn't about a single concept. search_semantic needs OKF_ENRICH_BASE_URL/OKF_ENRICH_MODEL(/OKF_ENRICH_API_KEY) set in the server's own environment, since an MCP tool call has no equivalent of a CLI flag to pass endpoint config through. Point it at a project root (defaults to .); it resolves the bundle the same way search/validate/graph do (okf.toml's output, or knowledge). Concept-consuming tools (everything except search/search_ranked) go through a per-session cache keyed on a cheap, content-free fingerprint of the bundle directory (file count, total size, latest modification time) — a chatty back-and-forth session reuses the same parsed bundle across calls instead of re-parsing it from scratch each time, while a generate run between two calls still changes the fingerprint and forces a fresh parse, so it always reflects the latest bundle without needing a server restart.

Register it with Claude Code:

claude mcp add okf-rs -s project -- /path/to/okf-mcp /path/to/project

-s project writes the registration to .mcp.json at the repo root so it's shared via git with every contributor's Claude Code instance, instead of only the local user's.

Register it with opencode by adding it to opencode.json:

{
  "mcp": {
    "okf-rs": {
      "type": "local",
      "command": ["/path/to/okf-mcp", "/path/to/project"]
    }
  }
}

or point any other MCP client's stdio transport at the same binary and argument.

Run okf-mcp <project> --benchmark instead of registering it with a client to get a local, offline session-level cost report for that project's own bundle: the fixed token cost of registering this server's tool schemas, a sample of real "who calls this?" queries comparing that against a naive grep-and-read baseline, and the resulting break-even point — no LLM call, no client needed. See ROADMAP.md for what it does and doesn't measure.

okf-mcp --benchmark-tool-selection answers a different question — not "is registering this server worth it," but "does a model pick the right relation inside the consolidated graph tool as reliably as it used to pick the right tool name," before that 18→6 consolidation happened. Opt-in and never run in CI (it needs a real model call): point it at any OpenAI-compatible /chat/completions endpoint with function-calling support via OKF_BENCHMARK_MODEL_BASE_URL/OKF_BENCHMARK_MODEL(/OKF_BENCHMARK_MODEL_API_KEY) — deliberately separate variables from search_semantic's OKF_ENRICH_*, so the benchmarked model and the enrichment model can differ — and it runs a fixed 14-question set against both the live graph tool and 13 reconstructed pre-consolidation graph_* tools on a known-correct fixture bundle, reporting tool-selection accuracy, final-answer accuracy, tokens, and latency for each design. A wrong selection is reported as one of three different-cost outcomes, not one undifferentiated "wrong": [LOUD-FAIL] (no tool matched, or a schema/argument the model or tool itself rejected — visible and retryable), [DETECTABLE-WRONG] (the wrong tool/relation, but its response is itself one of okf-query's own empty/negative "nothing found" results — a signal a downstream consumer could act on without knowing the right answer), or [SILENT-WRONG] (a well-formed call to the wrong tool/relation whose response is real, populated data — the expensive one, since nothing about the response itself signals a retry is needed). It also reports requests_per_answered_question1 / final-answer accuracy, cost expressed in requests rather than tokens, so it stays comparable regardless of session length. See docs/improvement-plan-provenance-diff.md for the full design, its Phase G for the failure-mode/requests-per-answered-question follow-up, and ROADMAP.md for how it was verified.

Why this reduces token consumption

Without okf-rs, an agent answering "who calls verify_token?" has to grep for the name, then open every file that matches to read enough surrounding code to confirm which hits are real call sites — each opened file costs its full size in context tokens, and a large file costs that every single time it's reopened across a session, including after a context compaction.

With okf-mcp, the same question is one graph tool call (relation: "callers") returning just the answer — no source file enters the agent's context at all. Concretely, on this repository itself: asking "who calls cmd_generate?" by hand means grep-ing to crates/okf-cli/src/main.rs and reading enough of that 672-line, ~24 KB file to find the answer (run) — roughly 6,000 tokens by the common ~4-chars-per-token rule of thumb. okf-rs graph callers functions/crates/okf-cli/src/cmd_generate (or the equivalent graph MCP call with relation: "callers") returns exactly one line, functions/crates/okf-cli/src/run — Rust Function — on the order of 15 tokens. That's not a one-off: it's the same gap on every call-graph/API-surface question an agent asks, and it compounds over a long session, since each grep-and-read round re-pays a whole file's token cost while each MCP call stays cheap regardless of how many times it's made — the expensive part (parsing and resolving the call graph) already happened once, at okf-rs generate time, not on every query.

The same logic applies beyond MCP: even just pointing an agent at the knowledge/ bundle's index.md files (no MCP server registered) means it's skimming pre-extracted signatures and relationships instead of full implementation bodies, comments, and boilerplate — a smaller, more targeted read for the same "what does this module expose" question.

There's a second, session-wide token cost that's easy to miss when looking only at per-query numbers like the one above: every tool okf-mcp registers contributes its full JSON Schema to the system prompt for the entire session, used or not. Before the graph tool consolidation, tools/list returned 18 tools at 6,238 bytes (~1,560 tokens); after, it's 6 tools at 4,863 bytes (~1,215 tokens) — a real, measured reduction, not just fewer names. That overhead is the other half of the token math a large per-query win like the one above doesn't capture on its own: on a small, already-familiar codebase, a session that never asks more than one or two structural questions can spend more on registering the tools than it saves answering them — see the MCP-init-cost/break-even benchmarking item in ROADMAP.md for the fuller session-level accounting this still needs.

Contributing

Issues and pull requests are welcome. Before opening a PR:

cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace

All of the above run in CI on every pull request.

License

Licensed under either of

at your option.