Code Search and Code Graph MCP Servers on FreeBSD: A 2026 Survey
Table of Contents
Companion to the larger "agent context" thread on wal.sh. The question: which code-search and code-graph tools have an MCP server, and which of those will actually run on a FreeBSD operator host (nexus) without dragging in Docker or a heavy JVM container?
Reference: GitNexus — the project that prompted the survey. It calls itself "the nervous system for agent context" and ships both a browser UI and a CLI+MCP server. Tree-sitter native bindings on the CLI side, WASM in the browser. Worth treating as the lighthouse for what "code graph as agent tool" means in 2026.
1. Why this matters
Most code-aware agent tooling today falls into two buckets:
- Search — grep/ripgrep over the working tree. Fast, low-context, misses cross-file relationships.
- Graph — knowledge graph of symbols/calls/imports. Slow to build, high-context, makes agents "see" architecture rather than chase strings.
The bridge between them in 2026 is MCP servers that expose either flavour as tool calls to an agent runtime (Claude Code, Cursor, Antigravity, Codex, Windsurf, OpenCode). The agent doesn't ingest the whole repo — it asks the server "where is X called?" and gets a focused answer.
2. The catalog
| tool | what | MCP | FreeBSD | URL |
|---|---|---|---|---|
| mcp-ripgrep | thin MCP wrapper over rg (stale ~14mo) |
yes | ripgrep in pkg; MCP is Node |
https://github.com/mcollina/mcp-ripgrep |
| ast-grep | Rust structural search via tree-sitter AST | yes (official ast-grep/ast-grep-mcp + ~8 community forks) |
buildable via cargo |
https://github.com/ast-grep/ast-grep |
| semgrep | static analysis / pattern lint, security focus | yes (in-tree at semgrep/cli/src/semgrep/mcp; standalone repo archived 2025-10-28; hosted "Guardian" at mcp.semgrep.ai) |
pip install semgrep (no port; OCaml wheel may need source build on FreeBSD) |
https://github.com/semgrep/semgrep/tree/develop/cli/src/semgrep/mcp |
| probe | Rust semantic search: ripgrep speed + tree-sitter AST | yes (built-in probe mcp) |
cargo build, or npx @probelabs/probe |
https://github.com/probelabs/probe |
| CodeGraphContext | indexes code into Neo4j graph (funcs/classes/calls/imports) | yes | Python; Neo4j 4.4 in pkg is EOL — needs Neo4j 5.x tarball | https://github.com/CodeGraphContext/CodeGraphContext |
| codebase-memory-mcp | single static binary, 158 langs via tree-sitter, persistent graph | yes | C/C++; needs cc/clang + tree-sitter grammars (NOT Go) |
https://github.com/DeusData/codebase-memory-mcp |
| code-graph-mcp (sdsrss) | AST knowledge graph: semantic search, call-graph, HTTP route tracing | yes | Rust + tree-sitter (cargo); license-unclear (no LICENSE in repo root) |
https://github.com/sdsrss/code-graph-mcp |
| wrale/mcp-server-tree-sitter | generic tree-sitter MCP: query, symbols, deps (ARCHIVED 2026-05-21) | yes | Python + tree-sitter, all in pkg | https://github.com/wrale/mcp-server-tree-sitter |
| GitNexus | browser graph + CLI/MCP with 16 tools (11 per-repo + 5 group) | yes | Node/TS; node24 in pkg; license PolyForm-NC (NOT OSS) |
https://github.com/abhigyanpatwari/GitNexus |
| Sourcegraph MCP | hosted code search; canonical URL is marketing-only | yes (remote, Enterprise/sales-gated) | server-side hosted; only HTTP client on nexus | https://sourcegraph.com/mcp |
| codebadger | Joern CPG (12+ langs) via MCP, Dockerized; ICSE 2026 workshop accepted (GPL-3.0) | yes | needs JVM + container — better in bastille jail |
https://github.com/Lekssays/codebadger |
| mcp-joern (sfncat) | simpler Joern MCP — code review + security | yes | same as above (JVM-based) | https://github.com/sfncat/mcp-joern |
| etags + xref (Emacs) | native definition index; no MCP server, but emacs --batch / emacsclient --eval is a tool call |
no (drive directly) | editors/emacs in pkg; etags ships in-tree |
https://www.gnu.org/software/emacs/manual/html_node/emacs/Tags-Tables.html |
Five catalog corrections were made before publish; see §10 for the per-tool verdicts and the agents' raw evidence rows.
3. Already on nexus
ripgrep-15.1.0_4 ; latest pkg is _6 (minor revbump only)
universal-ctags-p6.2.20260329.0 ; matches current quarterly
tree-sitter (binary) ; devel/tree-sitter port, latest 0.26.9
tokei ; devel/tokei 14.0.0_4
neo4j-4.4.44_2 ; matches port -- BUT 4.4 series is EOL upstream
openjdk21-21.0.10+7.1_1 ; matches exactly
py311-tree-sitter-0.20.4_1 ; matches; upstream is on 0.23+
scala-3.3.7 ; matches (pulls openjdk25 transitively, not 21)
Two FreeBSD-specific gaps the original draft glossed over:
- Neo4j 4.4 is EOL upstream. The
databases/neo4jport is pinned to the 4.4.x series; Neo4j itself is on 5.x (and 6.x exists). The CodeGraphContext install path assumes 5.x semantics (auth model, Cypher syntax, APOC bundling), so pursuing it on nexus would mean either running an unpatched EOL DB from pkg or installing 5.x from the upstream tarball outside the pkg system. Neither is great. - semgrep has no port at all — not in
devel/semgrep, not insecurity/semgrep. Thepip install semgreppath the draft mentions works but pulls ~80 transitive Python deps and, on FreeBSD, sometimes has to build OCaml wheels from source because the precompiled wheels target Linux. Practical, but not "in pkg" in the spirit the rest of this list reads.
Everything else in the on-nexus list checks out against FreshPorts with the usual one- or two-revision lag that comes from installing during an upgrade window.
4. Decision: install GitNexus and probe first
Rationale:
- GitNexus is the reference project that prompted the survey. It ships
a complete claude-plugin (
gitnexus-claude-plugin/), MCP config (.mcp.json), and skills (.claude/skills/gitnexus/). Even if we end up preferring something else, having it installed lets us read its skills and learn the pattern. Node monorepo,npm install -g gitnexus. The 16-tool claim (11 per-repo + 5 group/cross-repo) is verified against its README. License caveat: PolyForm-Noncommercial-1.0.0, so commercial use would need a separate licence — fine for personal research, worth knowing. - probe is the closest "ripgrep with brains" option. Rust, built-in
MCP, no Docker, no JVM, no Neo4j. Pure local search with AST
awareness. Fastest path to "agent has a smarter grep".
cargo build, or the upstream-recommendednpx @probelabs/probe.
Skipped for now:
- mcp-ripgrep: too thin — agents already get
Grepbuilt into Claude Code; an MCP wrapper adds nothing the existing tool doesn't give us. Also ~14 months since last push. - CodeGraphContext: requires standing up a Neo4j instance and embeddings infra. Worth it once we have agents asking real architectural questions, premature now — and gated on the Neo4j 5.x story above.
- ast-grep: parallel-tracks probe (both Rust + tree-sitter). Pick one to learn first; probe wins because its MCP is built in, not a separate wrapper.
- Joern path (codebadger / mcp-joern): heavy. Push to hydra sandbox per nexus operator model — nexus shouldn't run container workloads.
- Sourcegraph MCP: hosted MCP is sales-gated (Enterprise), the
SRC_ACCESS_TOKENpath applies to self-hosted Sourcegraph instances. Save for repos already on Sourcegraph Enterprise. - wrale/mcp-server-tree-sitter: archived 2026-05-21. Not a runner.
5. Install plan
5.1. GitNexus
# Already cloned at ~/ghq/github.com/abhigyanpatwari/GitNexus
sudo npm install -g gitnexus # registry: gitnexus@1.6.8 stable, 1.6.9-rc.15
gitnexus --version
# Wire MCP in ~/.claude/settings.json after install
5.2. probe
# Either path works; upstream's recommended distribution is npm.
cd ~/ghq && ghq get https://github.com/probelabs/probe
cargo install --path ~/ghq/github.com/probelabs/probe # local-clone build
# or:
npx @probelabs/probe@latest mcp --help # upstream-recommended
probe --version
probe mcp --help
7. Open questions for the post
- How does an MCP-served code graph compare to an embeddings-served one in agent recall? GitNexus has both modes; CodeGraphContext is pure graph. Worth a side-by-side on the same repo — gated on the Neo4j 5.x question for the CGC half.
- Can multiple MCP servers coexist cleanly in
~/.claude/settings.jsonwithout tool-name collisions? (searchis going to be claimed by somebody.) - Does the JVM-based Joern path actually pay off for security-focused review, or is semgrep's pattern lint enough? The semgrep path is itself pip-only on FreeBSD, so neither is friction-free.
- Cross-repo group queries (GitNexus's pitch) are the interesting ask — most tools above only index one repo at a time. Worth benchmarking.
The follow-up field test on our own Clojure REPL ecosystem (gitnexus@1.6.8,
darwin/arm64) is folded in below; its headline number, zero Clojure symbols
extracted and zero cross-links across six repos, is what the catalog above
cannot show.
7.1. GitNexus on Clojure repos: field notes (2026-06-25)
The catalog above scores GitNexus on install friction and MCP surface. This
section reports what it does on the repos we actually run: six Clojure and
ClojureScript repos joined through one deps.edn REPL. Version under test:
gitnexus@1.6.8 on darwin/arm64, 2026-06-24/25.
7.1.1. TL;DR
GitNexus is a per-language code-graph tool. On our repos it is blind to
Clojure: it extracts file nodes and IMPORTS edges from .clj/.cljc/.cljs but
zero function or call symbols. It pays off only on the Python and JS that lives
alongside (scripts, experiments). Two operational fixes make it usable without
making it useful for Clojure: a committed .gitnexusignore (kills the
build-output noise) and --index-only (stops it editing tracked files).
No .gitnexus/ index exists on this box today (only .gitnexusignore is
committed), so the impact() mandate in CLAUDE.md cannot see the repo's core
language, and a fresh index would not change that: it would carry zero Clojure
symbols.
7.1.2. Platform support
| Platform | Result |
|---|---|
| darwin/arm64 | works; onnxruntime-node@1.27.0 has a build; full index |
| FreeBSD | blocked; npx gitnexus analyze fails with EBADPLATFORM onnxruntime-node@1.27.0 (wanted win32,darwin,linux) |
On nexus (FreeBSD 15) install fails before any indexing. The Rust-native
probe (probe-code) is the working local alternative there: no ONNX dependency
(its embedded LSP daemon needed a built binary, not cargo run).
7.1.3. Install gotchas (it edits your repo)
analyze is not read-only. By default it injects a GitNexus section into
AGENTS.md and CLAUDE.md and writes .claude/skills/gitnexus/. Worse, if you
point it at a subdirectory it treats that as the index root and writes there:
npx gitnexus analyze src --skip-git # -> creates src/AGENTS.md, src/CLAUDE.md, src/.claude/ (pollution inside source)
Fix: --index-only (skips all file injection), or --skip-agents-md /
--skip-skills for parts. The gitnexus-analyze target at Makefile:783
bakes this in:
gitnexus-analyze: ## honors .gitnexusignore; --index-only = no AGENTS.md/CLAUDE.md/skills injection (darwin-only)
@command -v npx >/dev/null 2>&1 || { echo "npx required" >&2; exit 1; }
npx -y gitnexus analyze --index-only --force
7.1.4. Scoping: .gitnexusignore (the exclude answer)
GitNexus reads .gitignore by default (GITNEXUS_NO_GITIGNORE=1 to skip) and a
.gitnexusignore on top (.gitignore-style, negation with ! supported). Since
our compiled bundles are committed (they deploy), .gitignore cannot help;
.gitnexusignore is the lever. Ours:
dist/ build/ node_modules/ site/ # org content + compiled shadow-cljs bundles (generated) _drafts/ _queue/ archive/ papers/ worktrees/
Effect on www.wal.sh: the exclude finds the sweet spot.
| Scope | files | nodes | clusters | flows | what it is |
|---|---|---|---|---|---|
| whole repo | 1841 | 11,095 | 909 | 278 | ~98% minified site/static/js bundles |
analyze src only |
121 | 170 | 0 | 0 | flat .clj import graph |
.gitnexusignore |
374 | 1,390 | 79 | 79 | the Python + JS; Clojure still flat |
The universal derived-vs-code marker is .gitattributes
linguist-generated=true on the compiled bundles (GitHub collapses diffs and
drops them from language stats). GitNexus ignores it, hence the
.gitnexusignore.
7.1.5. The hard finding: zero Clojure symbols
The .gitnexusignore index has 341 functions. By language:
MATCH (n:Function)
RETURN
sum(CASE WHEN n.filePath CONTAINS '.clj' THEN 1 ELSE 0 END) AS clojure_functions,
sum(CASE WHEN n.filePath ENDS WITH '.py' THEN 1 ELSE 0 END) AS python_functions,
sum(CASE WHEN n.filePath ENDS WITH '.js' THEN 1 ELSE 0 END) AS js_functions,
count(n) AS total_functions
| clojure_functions | python_functions | js_functions | total_functions | | 0 | 115 | 226 | 341 |
Zero Clojure functions from 121 .clj/.cljc/.cljs files. GitNexus models them as
File nodes with IMPORTS edges only: no defs, no call graph, so 0 clusters and 0
flows on pure Clojure. Confirmed on a second repo: tech-crawler indexes to
164 nodes / 149 edges / 0 clusters / 0 flows.
7.1.5.1. Can Clojure support be configured? No.
- No
--language/ grammar flag onanalyze. doctor:Ext install: load-only (offline) - no network install(cannot add a grammar).- It ships no
tree-sitter-clojuresymbol-extraction queries.
Confirmed against the source. GitNexus officially supports 13 languages:
TypeScript, JavaScript, Python, Java, Kotlin, C#, Go, Rust, PHP, Ruby, Swift, C,
C++. Clojure is not on the list
(abhigyanpatwari/GitNexus;
MarkTechPost coverage, 2026-04). So the 0 functions is not a config miss; the
grammar is not there. Nobody indexes Clojure with GitNexus because it cannot.
7.1.5.2. The Clojure-capable sibling
Gortex ("From GitNexus to Gortex", zzet.org;
source at zzet/gortex) is a Go
re-implementation on the same principles that does support Clojure (plus Scala,
Elixir, Erlang, Haskell, OCaml, and others). If the goal is a code graph for the
clj ecosystem, evaluate Gortex, not GitNexus.
7.1.6. Our Clojure REPL ecosystem
The combined REPL (deps.edn :local/root aliases) spans six repos:
| Alias | Repo | ghq path |
|---|---|---|
| (host) | jwalsh/www.wal.sh | github.com/jwalsh/www.wal.sh |
:tech-crawler |
jwalsh/tech-crawler | github.com/jwalsh/tech-crawler |
:flight-tracker |
aygp-dr/flight-tracking | github.com/aygp-dr/flight-tracking |
:lemmata |
aygp-dr/lemmata | github.com/aygp-dr/lemmata |
:reversible |
aygp-dr/reversible-meta-synthesis | github.com/aygp-dr/reversible-meta-synthesis |
:aoc |
aygp-dr/advent-of-code | github.com/aygp-dr/advent-of-code |
All six are checked out at these ghq paths (the relative ../ and
../../aygp-dr/ paths the aliases resolve to). Load combined:
clj -A:repl:tech-crawler:flight-tracker:lemmata:reversible:aoc.
7.1.7. The group (cross-index) feature
gitnexus group does cross-repo impact analysis: group sync extracts
contracts (HTTP/gRPC/topics/shared-libs) and builds cross-links between member
indexes. Setup for the REPL ecosystem:
gitnexus group create www-clj-repl # -> ~/.gitnexus/groups/www-clj-repl/group.yaml
# index each member first (so it's in the registry), --index-only to avoid pollution:
for d in tech-crawler ../aygp-dr/flight-tracking ../aygp-dr/lemmata \
../aygp-dr/reversible-meta-synthesis ../aygp-dr/advent-of-code; do
( cd "$d" && npx -y gitnexus analyze --index-only ); done
# add members (groupPath = hierarchy, registryName = name from `gitnexus list`):
gitnexus group add www-clj-repl repl/host www.wal.sh
gitnexus group add www-clj-repl repl/tech-crawler tech-crawler
# ... then:
gitnexus group sync www-clj-repl
gitnexus group status www-clj-repl
group.yaml knobs: detect: {http, grpc, topics, shared_libs, embedding_fallback}
and matching: {bm25_threshold 0.7, embedding_threshold 0.65, ...}.
Result (synced 2026-06-25, 6 members). The per-repo node counts already show the pattern: clusters and flows track the non-Clojure code.
| Member | nodes | clusters | flows | (mostly) |
|---|---|---|---|---|
| repl/host (www.wal.sh) | 1,390 | 79 | 79 | python + js |
| repl/tech-crawler | 164 | 0 | 0 | pure clj |
| repl/flight-tracking | 652 | 1 | 0 | clj + a little py |
| repl/lemmata | 423 | 3 | 2 | clj + py |
| repl/reversible | 377 | 37 | 25 | python-heavy |
| repl/aoc | 1,304 | 18 | 20 | polyglot |
group sync reports 9 contracts, 0 cross-links. Every one of the 9 contracts
comes from a single repo's Python (repl/reversible, server.py plus fetch
calls):
[provider] http::GET::/api/<language>/<function> (repl/reversible) server.py
[consumer] http::POST::/{param}/api/v1/synthesize (repl/reversible) fetch
... 7 more, all repl/reversible / Python ...
Cross-links (0)
This is the verdict in one number: 0 cross-links across six repos that
genuinely depend on each other (www.wal.sh :local/root-requires all five).
GitNexus cannot see the ns~/~require graph that is the actual dependency
structure, so the cross-index registry is empty for a Clojure ecosystem.
7.1.8. Visualization
GitNexus can render; it just cannot render us:
gitnexus serve: local web UI athttp://localhost:4747(interactive graph; MCP HTTP at/api/mcp).- Browser build: drop a repo or ZIP at the GitNexus site, get a client-side graph plus a Graph-RAG agent.
gitnexus wiki: LLM-generated repo wiki from the graph (needs an LLM provider and API key).
All three visualize what it parsed: the Python/JS surfaces and a flat Clojure
import graph. The layer with no visibility, the shared-REPL :local/root
dependency structure (our inspection layer), is exactly what the group proved
invisible (0 cross-links). To visualize that, draw the deps.edn :local/root
graph ourselves: the host www.wal.sh REPL pulling all five repos via their
aliases, annotated with each repo's GitNexus node/cluster/flow counts. One
Graphviz block shows the structure GitNexus's whole group machinery could not.
Figure 1: The shared-REPL inspection layer: www.wal.sh pulls five repos via deps.edn :local/root aliases. GitNexus group sync reported 0 cross-links across this graph.
For an automated Clojure-aware graph, use a Clojure-supporting tool (Gortex). GitNexus is the wrong lens for the clj inspection layer.
7.1.9. Verdict
- Use it everywhere, expect language-specific value. Drop a
.gitnexusignoreand run--index-onlyin every repo; that is the portable setup. - Not for our Clojure core. 0 symbols, 0 flows; our own
wal-sh.site.orgparser (andprobefor search) already do what GitNexus cannot here. - Worth it where the repo has TS/JS/Python/Java-family code; that is where the call graph, clusters, impact, and group contracts actually populate.
7.1.10. Commands appendix
gmake gitnexus-analyze # --index-only --force, honors .gitnexusignore
npx -y gitnexus list # registry
npx -y gitnexus doctor # platform + embedding capabilities
npx -y gitnexus clean # delete index for current repo
npx -y gitnexus analyze --pdg --embeddings # deeper: control-flow graph + semantic (slow; still 0 for clj)
ghq get -u -p jwalsh/tech-crawler aygp-dr/flight-tracking ... # update the ecosystem checkouts
8. References
- GitNexus — reference project; lighthouse for "code graph as agent tool"
- probe — Rust, built-in MCP (also
@probelabs/probeon npm) - ast-grep — structural search; official MCP at ast-grep/ast-grep-mcp
- semgrep MCP (in-tree) — moved here after
semgrep/mcpwas archived 2025-10-28 - CodeGraphContext — Neo4j-backed code graph MCP (needs 5.x)
- codebase-memory-mcp — C/C++ binary, 158 langs
- code-graph-mcp (sdsrss) — Rust AST KG with HTTP route tracing
- wrale/mcp-server-tree-sitter — generic tree-sitter MCP (archived)
- codebadger — Joern CPG MCP, ICSE 2026 workshop, GPL-3.0
- mcp-joern — Joern MCP, lighter
- mcp-ripgrep — thin
rgwrapper (stale) - Sourcegraph MCP — hosted, Enterprise-gated
- Emacs manual: Tags Tables —
etags,xref, andetags-regen-mode - FreshPorts: ripgrep | FreshPorts: neo4j | FreshPorts: py-tree-sitter
9. Provenance
Cataloged 2026-06-24 by claude-agent on nexus, day-of after the 15.0 to 15.1 upgrade window closed. Survey scoped to "what can a FreeBSD operator host run without leaning on Docker" — a real constraint, not a purity flex. The Joern/Docker exclusion is operational (nexus is read-only ops; container workloads belong on hydra), not a judgement on the tools themselves.
Two parallel verification sub-agents (a719 for upstream repo + MCP claims; aacb for FreeBSD ports availability) ran before publish and corrected five factual errors in the original catalog table. Their raw verdicts are in §10 below.
Next action: install GitNexus + probe per §5, capture install notes, then write the wal.sh follow-up post on the side-by-side benchmark question.
10. Verification appendix
Per-tool verdicts from the upstream-claims verification pass (agent a719). The line format matches the property-drawer convention used elsewhere in the corpus.
TOOL: mcp-ripgrep ACTIVE: 2025-04-29 (~14mo stale, not archived) TOOL: ast-grep MCP: official ast-grep/ast-grep-mcp (424★) + ~8 community TOOL: semgrep mcp ACTIVE: standalone repo ARCHIVED 2025-10-28; in-tree fork active TOOL: probe ACTIVE: 2026-06-12; upstream recommends npm not cargo TOOL: CodeGraphContext ACTIVE: 2026-06-12; matches catalog TOOL: codebase-memory-mcp LANG: WRONG: C/C++ (31MB C, 7.8KB Go), not Go TOOL: code-graph-mcp LANG: WRONG: Rust (Cargo.toml/src/), not Python TOOL: wrale/mcp-server-tree-sitter ACTIVE: ARCHIVED, last push 2026-05-21 TOOL: GitNexus LICENSE: PolyForm-Noncommercial-1.0.0 (not OSS) TOOL: Sourcegraph MCP AUTH: Enterprise / sales-gated; no public install docs TOOL: codebadger PAPER: ICSE 2026 workshop verified; GPL-3.0 TOOL: mcp-joern ACTIVE: 2026-04-17 (~2mo stale); accurate as catalogued
FreeBSD-pkg pass (agent aacb) cross-referenced every entry in the "already on nexus" list against FreshPorts. Two flags carried forward into the main text:
databases/neo4jis pinned at 4.4.x (EOL upstream).- No
devel/semgreporsecurity/semgrepport; semgrep is pip-only, with an OCaml wheel that sometimes builds from source on FreeBSD.
Three secondary findings recorded but not promoted to the main text because they don't change the install plan:
py-tree-sitterport is on 0.20.4, upstream is 0.23+; may matter for newer grammar packs.lang/scala3.3.7 pullsopenjdk25as a transitive dep, not 21.- FreeBSD default
pythonstill points at 3.9 via the meta-port; the catalog'spy311-prefixes are correct and necessary.