MR

molaco/rust-code-mcp

Developer tools
32 stars 5 forks Quality 60 Trend 34

mcp server for rust code semantic hybrid search bm25 + embeddings with tree-sitter, gpu accelerated indexing and merkle tree updates

Overview

An MCP server for searching and analyzing Rust codebases. Combines hybrid BM25 + vector search with a HIR-driven workspace built on rust-analyzer, exposing 45+ tools for symbol navigation, call-graph traversal, structural audits, and semantic neighborhood queries. Website · Discord — come share how you're using it; we want to hear about people's workflows. See .docs/ARCHITECTURE.md for the per-module breakdown, TOOLS.md for the full MCP tool reference, .docs/configure-models-guide.md for embedding-model configuration, and THEORY.md for the principles each diagnostic maps to.

README

rust-code-mcp

An MCP server for searching and analyzing Rust codebases. Combines hybrid BM25 + vector search with a HIR-driven workspace hypergraph built on rust-analyzer, exposing 45+ tools for symbol navigation, call-graph traversal, structural audits, and semantic neighborhood queries.

Links: Website · Discord — come share how you’re using it; we want to hear about people’s workflows.

Architecture

See .docs/ARCHITECTURE.md for the per-module breakdown, TOOLS.md for the full MCP tool reference, .docs/configure-models-guide.md for embedding-model configuration, and THEORY.md for the principles each diagnostic maps to.

Features

  • Hybrid search - BM25 keyword search + semantic vector similarity (RRF fusion)
  • Pluggable embedding models - local GPU (Qwen3 via Candle/CUDA), local CPU (BGE via ONNX), or API-backed (OpenRouter); new API models are added through a config file with no recompile
  • Symbol navigation - rust-analyzer–backed find_definition / find_references / rename_symbol (rename returns a preview; no files are modified)
  • Persisted hypergraph - HIR-driven workspace snapshot (LMDB) with cross-crate imports, exports, re-exports, call edges, attributes, signatures, statics, and unsafe blocks
  • Call-graph traversal - who_calls / calls_from / call_graph / callers_in_crate / recursive_callers_count
  • Structural audits - dead public items, name collisions, module shadowing, forbidden cross-crate edges, Robert Martin instability/abstractness
  • Safety audits - unsafe_audit, mut_static_audit, recursion_check, channel_capacity_audit, fn_body_audit (unwrap/panic/lock-across-await detection)
  • Doc & API hygiene audits - missing_docs_audit, derive_audit, pub_use_pub_type_audit, re_export_chain
  • Semantic neighbors - similar_to_item and workspace-wide semantic_overlaps clustering via cached embeddings
  • Codemap - build_codemap produces a task-conditioned subgraph (seeded by symbols, expanded over hypergraph edges) with Mermaid + outline rendering
  • Complexity metrics - LOC, cyclomatic complexity, function counts
  • Incremental indexing - Merkle-tree change detection; background re-sync every 5 minutes

Tools

45+ MCP tools grouped by category. Full parameter reference in TOOLS.md.

Category Tools
Query search, get_similar_code, read_file_content
Symbol analysis find_definition, find_references, rename_symbol, get_dependencies, get_call_graph, analyze_complexity
Index lifecycle index_codebase, health_check, clear_cache
Hypergraph build build_hypergraph
Imports / exports get_imports, get_exports, get_reexports, get_declared_reexports
Reverse lookup who_imports, who_uses, who_uses_summary
Call graph who_calls, calls_from, call_graph, callers_in_crate, recursive_callers_count
Workspace structure dead_pub_in_crate, dead_pub_report, crate_edges, overlaps, module_tree, crate_types, crate_skeleton, workspace_stats
Architecture rules forbidden_dependency_check, crate_dependency_metric
Signatures & attributes function_signature, functions_with_filter, enum_variants, item_attributes, items_with_attribute
Safety & quality audits unsafe_audit, mut_static_audit, recursion_check, channel_capacity_audit, fn_body_audit
Doc / API audits missing_docs_audit, derive_audit, pub_use_pub_type_audit, re_export_chain
Semantic similar_to_item, semantic_overlaps
Codemap build_codemap

Skills

The skills/ directory ships 26 Claude Code skills that compose these MCP tools into ready-made audit recipes. Each skill is a self-contained SKILL.md with prerequisites, step-by-step prompts, and hand-offs to related skills — invoke them in Claude Code as / once installed.

Scope Skill Purpose
Workspace rmc-workspace-overview First-look audit of a Rust workspace
Workspace rmc-architecture-rules Enforce crate-edge rules
Workspace rmc-dependency-metric Rank crates by Robert Martin instability / abstractness
Workspace rmc-imports-exports Cross-crate imports and exports audit
Workspace rmc-type-overlaps Name collisions and module shadows
Workspace rmc-semantic-overlaps Duplicate logic detection by embedding similarity
Workspace rmc-snapshot-diff Compare two workspace snapshots (e.g. across branches)
Crate rmc-crate-audit Audit one crate end-to-end
Crate rmc-api-surface Audit a crate’s public API
Crate rmc-module-audit Audit a single module
Symbol rmc-find-symbol Resolve a symbol’s qualified name
Symbol rmc-symbol-forensics Deep dive on one symbol (refs, callers, attrs)
Symbol rmc-method-api Audit a type’s methods
Symbol rmc-trait-audit Audit a trait and its impls
Symbol rmc-enum-variants Inspect an enum’s variants
Symbol rmc-rename-symbol Preview a rename — exact reference set & refactor probe
Symbol rmc-signature-search Find fns by signature shape
Quality rmc-unsafe-audit Audit unsafe blocks
Quality rmc-mut-static-audit Audit global mutable state
Quality rmc-attribute-audit Audit attributes and doc-comments
Quality rmc-complexity Complexity hotspots by blast radius
Quality rmc-test-vs-prod Test vs production split
Quality rmc-call-graph Fn-level call graphs
Quality rmc-reexport-chain Trace pub use re-export chains
Planning rmc-codemap Task-conditioned workspace subgraph (nodes / edges / hierarchy)
Planning rmc-refactor-plan Plan a refactor with evidence from the hypergraph

Install by symlinking (or copying) the directories into your Claude Code skills folder:

mkdir -p ~/.claude/skills
ln -s "$(pwd)/skills/"rmc-* ~/.claude/skills/

Then in Claude Code, type /rmc- to discover them, or /rmc-workspace-overview to kick off a tour of a new repo.

Installation

1. Build the binary

git clone https://github.com/molaco/rust-code-mcp.git
cd rust-code-mcp
cargo build --release

The binary is at target/release/rust-code-mcp.

Build prerequisite: the default embedding backend (Qwen3) runs on Candle with CUDA, so the build needs the CUDA toolkit (nvcc) on PATH. The simplest way to get a correct toolchain is the Nix dev shell (see Nix) — nix develop provides nightly Rust plus the CUDA toolkit, cuDNN, and cuBLAS. See GPU & CUDA for details.

Optionally, copy it somewhere on your PATH:

cp target/release/rust-code-mcp ~/.local/bin/

2. Add to Claude Code

In your Rust project directory, create .mcp.json:

{
  "mcpServers": {
    "rust-code-mcp": {
      "command": "/absolute/path/to/rust-code-mcp"
    }
  }
}

Or add it globally in ~/.claude.json so it’s available in all projects:

{
  "mcpServers": {
    "rust-code-mcp": {
      "command": "/absolute/path/to/rust-code-mcp"
    }
  }
}

3. Index your codebase

Once Claude Code starts, the server is running. Use the index_codebase tool to index your project:

> index my codebase at /absolute/path/to/my-rust-project

Or call the tool directly with the directory parameter set to your project root. Pass an optional embedding_profile to choose the embedding model (see Embedding Models); the default is a local GPU model. Indexing is incremental — subsequent runs only process changed files (via Merkle tree change detection). A background sync also re-indexes every 5 minutes automatically, using the profile each index was built with.

4. Start using it

All tools accept a directory parameter pointing to your project root. Examples:

  • Search code: search with a query like “error handling in parser”
  • Find definitions: find_definition for a symbol name
  • Find references: find_references to see all usages of a symbol
  • Preview a rename: rename_symbol returns the full edit set without touching files
  • Call graph: get_call_graph or who_calls / calls_from to trace function relationships
  • Skeleton facade: crate_skeleton writes a stripped mirrored source tree under .skeleton/
  • Similar code: get_similar_code for semantic similarity search

For Rust-specific workspace analysis, first call build_hypergraph once (reuses a fingerprinted snapshot on subsequent calls), then run audits like unsafe_audit, dead_pub_report, overlaps, crate_dependency_metric, or semantic_overlaps. The codemap tool (build_codemap) produces a Mermaid-renderable subgraph seeded by symbols of interest. The skeleton tool (crate_skeleton) writes generated facade files to /.skeleton/, mirroring source-relative paths.

Index data is stored in ~/Library/Application Support/dev.rust-code-mcp.search/ (macOS) or ~/.local/share/search/ (Linux), keyed by a hash of the project path and the active embedding profile — so different profiles get independent indexes. It does not write index/cache data to your project directory; crate_skeleton is the explicit exception and writes generated files under .skeleton/. The persisted hypergraph lives alongside the index data (under graph//, in LMDB). clear_cache with include_hypergraph=true wipes both.

Shared daemon (one server per project)

The stdio transport is 1:1 with its client by construction: every editor window or agent session spawns its own server process — and with it another copy of the loaded rust-analyzer context (~2 GB per workspace) plus another ONNX/GPU context. Measured on one developer machine before this landed: six live servers, 8.9 GB, all analyzing the same repository.

The runtime state was already shareable (RuntimeState is a bundle of Arcs, semantic contexts are cached per project path, and locking is per workspace), so the only missing piece was a transport that accepts more than one client:

  • running the binary with no arguments makes it a client of a per-project daemon, spawning that daemon on first use — no change to .mcp.json is needed;
  • the daemon serves every connection from one shared RuntimeState and exits after 30 minutes with no clients (--idle-secs / RMC_DAEMON_IDLE_SECS, 0 = never);
  • if the daemon cannot be reached or started, the client serves the session in-process, exactly as before — the daemon is a memory optimisation, not a new point of failure;
  • a session whose daemon dies mid-flight ends with exit code 1 and a message on stderr, instead of continuing quietly in a second server: stdin is already partly consumed, so a server started afterwards would answer a truncated stream;
  • a bad RMC_EMBEDDING_PROFILE exits 2 in every mode that serves a session, the client included, while --help and --print-socket answer normally and exit 0, because they serve nothing and are how a host reads back the value it got wrong.

The socket key covers the workspace root — the walk upwards takes the nearest Cargo.toml that declares a [workspace] and stops at a .git or .jj boundary, so two sessions started in different subdirectories of one repository share a daemon while a stray outer manifest cannot merge two repositories into one — the binary’s size and mtime, the resolved index root (data_dir()), because it decides where the index lives and is itself resolved from XDG_DATA_HOME and then HOME, LD_LIBRARY_PATH, because it decides which CUDA and ONNX libraries the daemon links at start, and every RMC_* and RUST_CODE_MCP_* variable plus OPENROUTER_API_KEY. The four transport knobs (RMC_DAEMON, RMC_DAEMON_DIR, RMC_DAEMON_LOG_DIR, RMC_DAEMON_IDLE_SECS) are excluded, because they choose which daemon a client talks to, or where it logs, rather than what it answers. A rebuilt binary, or a differently configured one, therefore gets its own daemon instead of silently attaching to one that answers differently.

The daemon runs in the project root it is keyed on, so every client of one daemon resolves a relative directory argument the same way, whichever subdirectory its session started in. A client whose own working directory is a subdirectory therefore sees "." mean the project root through the daemon and that subdirectory with RMC_DAEMON=0, so prefer absolute paths in tool arguments.

rust-code-mcp --print-socket   # which socket this project resolves to
rust-code-mcp --in-process     # previous behaviour (same as RMC_DAEMON=0)
rust-code-mcp --daemon         # run the daemon in the foreground

Sockets and locks live in $XDG_RUNTIME_DIR/rust-code-mcp/ (override with RMC_DAEMON_DIR). Daemon logs live on disk in $XDG_STATE_HOME/rust-code-mcp/logs/, normally ~/.local/state/rust-code-mcp/logs/ (override with RMC_DAEMON_LOG_DIR), named after the socket plus a short hash of its full path, with a .log extension, so two user-named sockets that share a name do not share a log — never in the runtime directory, which is a small tmpfs meant for sockets. The log is appended rather than replaced, so the crash of an earlier daemon stays readable after a new one starts, and the daemon truncates it in place before any tracing event that would take it past 32 MiB, so a long-lived daemon cannot fill a disk however verbose its RUST_LOG. The bound is read from the file, not counted: panic output, which reaches the same file through the daemon’s raw stderr, counts at the next event. A write the log cannot take is dropped, never reported through stderr. Either directory, when it already exists, is used as it is and never re-permissioned; only a directory the daemon creates gets owner-only access. A symlink, a path that is not a directory, and a directory owned by another user are refused. Unix only; on other platforms the server stays in-process.

Embedding Models

Semantic search and the embedding-backed audits (get_similar_code, similar_to_item, semantic_overlaps) run on a configurable embedding profile. Built-in profiles:

Profile Model Runtime Dim
local-cpu-small (default) BGE-small-en-v1.5 local ONNX/CPU 384
local-gpu-small Qwen3-Embedding-0.6B local Candle/CUDA 1024
local-qwen3-4b Qwen3-Embedding-4B local Candle/CUDA 2560
local-qwen3-8b Qwen3-Embedding-8B local Candle/CUDA 4096
openrouter-qwen3-8b Qwen3-Embedding-8B OpenRouter API 4096

The default when embedding_profile is omitted is local-cpu-small. Select another profile by passing embedding_profile to index_codebase and the search tools. Each profile gets its own independent index, and search must use the profile its index was built with.

Set the default with RMC_EMBEDDING_PROFILE — this environment variable names the profile used whenever a call omits embedding_profile, and changing it needs no rebuild:

RMC_EMBEDDING_PROFILE=local-gpu-small rust-code-mcp

Built-in profiles only: the canonical names above, their legacy aliases, and any letter case. A profile from embedding_profiles.toml needs the embedding_profile argument instead. Unset or blank keeps local-cpu-small, and whitespace is trimmed. The value is read once at startup, and an unusable name — including a local CUDA profile in a build without CUDA support — stops the server with one stderr line and exit code 2 rather than a silent fall back, because the profile is part of the index identity. Background sync is unchanged: it keeps syncing the indexes already on disk, and only a new local-CUDA index stays out of it.

API models (OpenRouter) require an API key in the environment — keys are never read from config files:

export OPENROUTER_API_KEY=sk-or-...

Adding an API model is a config change, no recompile — drop an embedding_profiles.toml in your project root:

[[profile]]
name     = "openrouter-text-embedding-3-small"
model_id = "openai/text-embedding-3-small"
dim      = 1536
max_len  = 8191

Local models (Candle/ONNX) are code-bound and ship as built-ins. See .docs/configure-models-guide.md for the full TOML schema, OpenRouter tuning knobs, and the trade-offs between models.

Nix

A Nix flake is provided for easy setup:

# Enter dev shell with all dependencies
nix develop github:molaco/rust-code-mcp

# Build the binary
nix build github:molaco/rust-code-mcp

The dev shell includes nightly Rust, ONNX Runtime, and the full CUDA toolchain (toolkit, cuDNN, cuBLAS) needed when you opt into the GPU embedding path.

GPU & CUDA

The default embedding profile (local-cpu-small, BGE) runs on ONNX/CPU and needs no GPU. Local Qwen3 profiles (local-gpu-small, local-qwen3-4b, local-qwen3-8b) run on Candle with CUDA; OpenRouter profiles offload embedding to the API and need no local GPU.

Requirements

  • NVIDIA GPU — ≥8 GB VRAM is comfortable for the default 0.6B model; the local 8B profile needs ~16 GB
  • CUDA toolkit (nvcc) at build time — Candle’s CUDA backend (cudarc) requires it during compilation
  • CUDA runtime + cuDNN + cuBLAS libraries at run time

Setup

The Nix dev shell provides the full CUDA build and runtime environment — this is the supported path:

nix develop          # nightly Rust + CUDA toolkit + cuDNN/cuBLAS
cargo build --release --features cuda

When using a local CUDA profile and the MCP server is spawned by Claude Code (rather than launched from the Nix shell), its process still needs the CUDA runtime libraries on LD_LIBRARY_PATH. Set them in your MCP client config:

{
  "mcpServers": {
    "rust-code-mcp": {
      "command": "/path/to/rust-code-mcp",
      "env": {
        "RUST_LOG": "info",
        "CUDA_HOME": "/usr/local/cuda",
        "LD_LIBRARY_PATH": "/usr/local/cuda/lib64:/usr/lib/x86_64-linux-gnu"
      }
    }
  }
}

Adjust the paths to your CUDA installation.

Running without a GPU

A GPU is not required. The default build and default embedding profile are CPU-only:

cargo build --release

Index and search with the omitted/default profile or embedding_profile = "local-cpu-small" (BGE on ONNX/CPU). OpenRouter profiles also avoid local GPU use, but require OPENROUTER_API_KEY.

Performance

Indexing throughput measured on this repository (~2,280 chunks) with an RTX 3090:

Profile Indexing throughput Notes
local-gpu-small (Qwen3-0.6B) ~55 chunks/sec local GPU; private, deterministic
openrouter-qwen3-8b (Qwen3-8B) ~45–50 chunks/sec API; per-request latency varies run-to-run
openrouter text-embedding-3-small ~220 chunks/sec API; fastest, general-purpose (not code-tuned)

Embedding is ~95%+ of indexing time. Larger / higher-quality models embed more slowly; text-embedding-3-small is fastest but not code-tuned. Vector search is exact brute-force KNN — fast at workspace scale regardless of dimension. Pick a profile for your quality / speed / privacy trade-off; see the config guide.

Stack

  • tantivy - Full-text BM25 search
  • fastembed - Embedding models: Qwen3 via Candle (GPU), BGE via ONNX (CPU)
  • candle - GPU embedding runtime for Qwen3 (CUDA)
  • reqwest - OpenRouter API embedding client
  • lancedb - Embedded vector storage
  • ra_ap_syntax - AST parsing
  • ra_ap_ide - Semantic analysis (goto definition, find references, rename)
  • ra_ap_hir - HIR-driven hypergraph extraction
  • heed - LMDB-backed persisted hypergraph store
  • sled - Embedded KV for indexing metadata cache
  • rmcp - MCP protocol

Screenshots

License

MIT

View this README on GitHub

Install

This server does not publish a one-line install command.

Open the repository installation guide

Configuration

{ "mcpServers": { "rust-code-mcp": { "command": "/absolute/path/to/rust-code-mcp" } } }