pitlane-mcp
Token-efficient code intelligence MCP server. Indexes a codebase once using tree-sitter AST parsing and lets AI agents retrieve exactly the symbols they need — instead of dumping entire files into context.
Why
AI coding agents default to reading whole files. With pitlane-mcp, they fetch only the symbol they need — 532× less tokens on a Rust codebase (ripgrep), 418× on C++ (LevelDB), 133× on C (Redis), 125× on Go (Gin), 112× on Java (Guava), 65× on C# (Newtonsoft.Json), 61× on Ruby (RuboCop), 56× on Kotlin (OkHttp), 54× on Objective-C (SDWebImage), 53× on TypeScript (Hono), 52× on Swift (SwiftLint), 49× on Solidity (OpenZeppelin Contracts), 41× on Svelte (svelte.dev), 20× on Python (FastAPI), 80× on PHP (Laravel), 801× on Zig (zls), 90× on Lua (Roact), and Bash (bats-core)¹.
Features
- AST-based indexing — tree-sitter parses Rust, Python, JavaScript, TypeScript, Svelte (embedded
<script>/<script lang="ts">blocks only), C, C++, Go, Java, C#, Ruby, Swift, Objective-C, PHP, Zig, Kotlin, Lua, Solidity, and Bash source into structured symbols - BM25 full-text search — tantivy-backed ranked search over name, qualified name, signature, and doc fields with a custom camelCase tokenizer (
LowerInstruction→["lower", "instruction"]); falls back to exact substring match if the index isn't ready - Graph-aware navigation tools — direct callers and callees for shallow impact checks without whole-repo back-and-forth
- Thirteen MCP tools for navigation: outline, search, fetch, line-range fetch, callers, callees, usages, index stats, usage stats
- Incremental re-indexing — background watcher re-parses only changed files
- Disk-persisted index — binary format, loads in milliseconds on subsequent calls
- Smart exclusions — automatically skips
.venv,node_modules,target,__pycache__,dist,.next, and other dependency/build trees at any depth - Fully local — no network calls, no external APIs
Supported Languages
| Language | Extensions | Symbol kinds |
|---|---|---|
| Rust | .rs | function, method, struct, enum, trait, impl, mod, macro, const, type alias |
| Python | .py | function, method, class |
| JavaScript | .js, .jsx, .mjs, .cjs | function, class, method |
| TypeScript | .ts, .tsx, .mts, .cts | function, class, method, interface, type alias, enum |
| Svelte | .svelte | function, class, method, interface, type alias, enum (from embedded <script> / <script lang=\"ts\"> blocks only; template/style sections are not indexed) |
| C | .c, .h | function, struct, enum, type alias, macro |
| C++ | .cpp, .cc, .cxx, .hpp, .hxx | function, method, class, struct, enum, type alias, macro |
| Go | .go | function, method, struct, interface, type alias |
| Java | .java | class, interface, enum, method |
| C# | .cs | class, struct, interface, enum, method, type alias |
| Bash | .sh, .bash | function |
| Ruby | .rb | class, module, method |
| Swift | .swift | class, struct, enum, protocol, method, function, init, type alias |
| Objective-C | .m, .mm | class, protocol, method, function, type alias |
| PHP | .php | class, interface, enum, method, function |
| Zig | .zig | function, method, struct, enum, const |
| Lua | .luau, .lua | function, method, type alias |
| Kotlin | .kt, .kts | class, interface, enum, object, function, method, type alias |
| Solidity | .sol | contract, interface, library, function, method, modifier, constructor, event, error, struct, enum |
TypeScript declaration files (.d.ts, .d.mts, .d.cts) are automatically skipped.
Installation
Download a pre-built binary from GitHub Releases for Linux (x86_64, aarch64), macOS (x86_64, Apple Silicon), and Windows (x86_64).
Or install via Homebrew (macOS):
brew tap eresende/pitlane-mcp
brew install pitlane-mcp
Or install via cargo-binstall (pulls pre-built binaries, no compilation needed):
cargo binstall pitlane-mcp
Or install from crates.io (requires Rust 1.75+):
cargo install pitlane-mcp
Or build from source:
cargo build --release
cp target/release/pitlane-mcp ~/.local/bin/
cp target/release/pitlane ~/.local/bin/
MCP Client Configuration
Claude Code
claude mcp add pitlane-mcp -- pitlane-mcp
OpenCode
Add to your opencode.json or opencode.jsonc:
{
"mcp": {
"pitlane-mcp": {
"type": "local",
"command": ["pitlane-mcp"]
}
}
}
VS Code / Kiro IDE
Add to your MCP settings (.kiro/settings/mcp.json or .vscode/mcp.json):
{
"mcp": {
"servers": {
"pitlane-mcp": {
"type": "stdio",
"command": "pitlane-mcp",
"args": []
}
}
}
}
Tools
index_project
Parse and index all supported source files under a path.
{ "path": "/your/project", "force": false }
Returns symbol count, file count, index path, and elapsed time. Subsequent calls return cached results unless force: true.
Optional parameters:
exclude— additional glob patterns to skip during the walk (e.g."vendor/**").force: true— rebuild the index even if the on-disk copy is up to date.max_files— cap on the number of source files indexed (default: 100 000). Raise this for very large mono-repos. If the walk finds more eligible files than the cap,index_projectreturns aFILE_LIMIT_EXCEEDEDerror instead of indexing.
search_symbols
Search by name, kind, language, or file pattern.
{ "project": "/your/project", "query": "authenticate", "kind": "method" }
Defaults to BM25 ranked full-text search (via tantivy) over name, qualified name, signature, and doc fields — results are ordered by relevance. Pass "mode": "exact" for substring matching or "mode": "fuzzy" for trigram similarity. If the BM25 index isn't ready yet (e.g. first call after an upgrade), it falls back to exact automatically.
get_symbol
Retrieve the source of one symbol by its stable ID. Much cheaper than reading the whole file.
{ "project": "/your/project", "symbol_id": "src/auth.rs::Auth::login#method" }
Optional parameters:
signature_only— returns only the indexed metadata (signature, doc comment, file, line range) with no file I/O. Defaults totruefor struct, class, interface, and trait kinds; defaults tofalsefor functions, methods, and everything else. Passsignature_only: falseexplicitly to get the full body of a container type.include_context: true— includes 3 lines of surrounding source before and after the symbol.
Full-source responses include a references field — a list of symbols whose names appear as identifiers in the source. This saves a separate find_usages call when you want to understand what a symbol depends on.
Python/JS/TS/Java classes, C++ classes/structs, C# classes/structs, Ruby classes/modules, and Swift classes/structs: for classes that contain methods,
get_symbolreturns only the class header (plus docstring for Python) — not the full body. Objective-C@interfaceblocks are returned at full extent (they contain only declarations, not implementations). Retrieve individual methods by their own symbol IDs (e.g.models.py::MyClass::some_method#method). Useget_file_outlineto list all methods first.
get_file_outline
List all symbols in a file with kinds and line numbers — no source returned.
{ "project": "/your/project", "file_path": "src/auth.rs" }
get_project_outline
High-level overview of the project: files grouped by directory with symbol counts per kind.
{ "project": "/your/project", "depth": 2 }
Optional parameters:
summary: true— return only directory names with file and symbol counts, no per-file items or kind breakdowns. Use for very large codebases (>10k files) where the full outline exceeds token limits. Agents should retry with this flag if the normal response is too large.path— scope the outline to a subtree (e.g."kernel/sched"). Combine withdepthto drill into a specific area of a large repo.max_dirs— cap the number of directory entries returned (default: 50, max: 500). When the result is truncated, the response includes ahintsuggestingpathorsummary: true.
find_usages
Find all locations that reference a symbol by name.
{ "project": "/your/project", "symbol_id": "src/auth.rs::Auth::login#method" }
AST-based reference search — only true identifier nodes are matched. String literals, comments, and substrings of longer identifiers are never returned.
Svelte note: reference search only covers identifiers inside embedded
<script>/<script lang="ts">blocks. Template and style sections are intentionally ignored.
find_callees
Return direct outgoing references for one symbol — useful for seeing what a function or method likely calls before reading more code.
{ "project": "/your/project", "symbol_id": "src/auth.rs::Auth::login#method" }
Optional parameters:
limit— maximum callees to return (default: 100).offset— offset into callees for pagination.
This is intentionally shallow and lightweight. Results are heuristic direct references, not a full semantic call graph.
find_callers
Return direct incoming references for one symbol — useful for quick impact checks before changing a function or method.
{ "project": "/your/project", "symbol_id": "src/auth.rs::Auth::login#method" }
Optional parameters:
scope— restrict callers to a file or directory glob.limit— maximum callers to return (default: 100).offset— offset into callers for pagination.
Like find_callees, this stays shallow by design and returns heuristic direct callers, not a full transitive call graph.
get_lines
Fetch a slice of a file by line range — useful for blocks that are not named symbols (macro invocation tables, initializer arrays, inline comment blocks, etc.).
{ "project": "/your/project", "file_path": "fs/read_write.c", "line_start": 668, "line_end": 700 }
Returns source, total_file_lines, and the actual line_end after clamping. Capped at 500 lines per call; when the cap is hit the response includes truncated: true and a truncated_note with the next line_start to continue.
get_index_stats
Return symbol counts by language and kind for an indexed project — lightweight orientation tool. Use instead of get_project_outline when you only need aggregate numbers, not the file tree.
{ "project": "/your/project" }
Returns total_files, total_symbols, by_language, and by_kind, all sorted by count descending.
get_usage_stats
Return token-efficiency statistics for get_symbol calls — how many tokens were saved by signature-only responses, persisted across sessions.
{ "project": "/your/project" }
Returns global totals and a per-project breakdown with get_symbol_calls, signature_only_applied, full_source_bytes, returned_bytes, and tokens_saved_approx. Stats are stored at ~/.pitlane/stats.json.
watch_project
Start incremental background re-indexing on file changes.
{ "project": "/your/project" }
{ "project": "/your/project", "stop": true }
{ "project": "/your/project", "status_only": true }
Pass status_only: true to check whether a watcher is already running without starting or stopping it — returns "status": "watching" or "status": "not_watching".
CLI
The pitlane binary exposes the same code intelligence as the MCP server, useful for shell scripts, CI pipelines, or manual exploration.
pitlane index
Index a project (or load from cache if up to date).
pitlane index /your/project
pitlane index /your/project --force
pitlane index /your/project --exclude "*.generated.ts" --exclude "vendor/**"
pitlane index /your/project --max-files 200000
pitlane search
Search for symbols by name with optional filters.
pitlane search /your/project authenticate
pitlane search /your/project authenticate --kind method
pitlane search /your/project authenticate --lang python
pitlane search /your/project authenticate --file "src/auth*"
pitlane search /your/project authenticate --limit 5 --offset 10
pitlane outline
High-level directory/symbol overview of the project.
pitlane outline /your/project
pitlane outline /your/project --depth 3
pitlane outline /your/project --path src/auth --max-dirs 100
pitlane outline /your/project --summary
pitlane file
List all symbols in a file with kinds and line numbers.
pitlane file /your/project src/auth.rs
pitlane symbol
Fetch the source of a single symbol by its ID.
pitlane symbol /your/project src/auth.rs::Auth::login[method]
pitlane symbol /your/project src/auth.rs::Auth::login[method] --context
pitlane symbol /your/project src/auth.rs::Auth::login[method] --sig-only
pitlane callees
Show direct outgoing references for a symbol.
pitlane callees /your/project src/auth.rs::Auth::login[method]
pitlane callees /your/project src/auth.rs::Auth::login[method] --limit 20 --offset 20
pitlane callers
Show direct incoming references for a symbol.
pitlane callers /your/project src/auth.rs::Auth::login[method]
pitlane callers /your/project src/auth.rs::Auth::login[method] --scope "src/**" --limit 20
pitlane usages
Find all call sites for a symbol.
pitlane usages /your/project src/auth.rs::Auth::login[method]
pitlane usages /your/project src/auth.rs::Auth::login[method] --scope "src/**" --limit 20
pitlane lines
Fetch a specific line range from a file.
pitlane lines /your/project src/auth.rs 40 80
pitlane wait-embeddings
Block until background embedding generation has finished.
pitlane wait-embeddings /your/project
pitlane wait-embeddings /your/project --poll-interval-ms 1000 --timeout-secs 120
pitlane watch
Keep the project index updated until interrupted.
pitlane watch /your/project
Unlike the MCP server, the CLI watcher only lives for the lifetime of that pitlane watch process. Stop it with Ctrl-C.
pitlane usage-stats
Show token-efficiency statistics for get_symbol.
pitlane usage-stats
pitlane usage-stats /your/project
All commands output JSON to stdout. Logs go to stderr and can be controlled with RUST_LOG.
Symbol IDs
Symbol IDs are stable string keys derived from the file path, qualified name, and kind:
{relative_path}::{qualified_name}#{kind}
src/audio/engine.rs::Engine::process_block#method
src/models/user.py::UserService::authenticate#method
src/api/client.ts::fetchUser#function
src/components/Button.tsx::Button#function
IDs are returned by search_symbols and get_file_outline and used as input to get_symbol, find_callees, find_callers, and find_usages.
Index Storage
Indexes are stored at:
~/.pitlane/indexes/{project_hash}/index.bin
~/.pitlane/indexes/{project_hash}/meta.json
The project hash is a BLAKE3 hash of the canonical project path. The index is invalidated automatically when source files change (mtime-based). Use force: true to rebuild unconditionally.
Recommended Agent Instructions
Add a CLAUDE.md at your project root to guide the agent:
# Code Navigation
Use pitlane-mcp for all code lookups when available.
1. Call index_project at the start of each session to load the index.
2. Call watch_project right after indexing to keep the index up to date as files change.
3. Before reading any file, call get_file_outline to see its structure without consuming its full content.
4. Use search_symbols to find functions/types by name. If no exact match is found it falls back to fuzzy matching automatically.
5. Use get_symbol to retrieve only the exact implementation you need, not the whole file.
6. Use find_callees to see what a symbol directly depends on before opening more files.
7. Use find_callers before changing a symbol to get a shallow impact view.
8. Use find_usages before refactoring any public symbol or when you need exhaustive name-based matches.
9. For struct/class/interface/trait symbols, get_symbol returns signature-only by default. Pass signature_only=false to get the full body and the references list.
10. Use get_lines to fetch a specific block by line range when it isn't a named symbol.
11. Use get_index_stats to orient yourself in a new codebase without burning tokens on get_project_outline.
12. Fall back to direct file reads only when editing or when full file context is genuinely required.
Benchmarks
Each language is benchmarked against a pinned open-source project chosen for real-world representativeness. New corpora are added as language support grows.
Note: pitlane-mcp is under active development. New language support and token-efficiency optimizations land frequently, so these numbers are updated with each release and may change significantly between versions.
Test environment: AMD Ryzen 9 9950X (16 cores / 32 threads), 32 GB RAM, NVMe SSD.
Results
| Corpus | Language | Files | Symbols | Index time¹ | Token eff.² | search_symbols | get_symbol |
|---|---|---|---|---|---|---|---|
| ripgrep 14.1.1 | Rust | 101 | 3,207 | 248 ms | 532× | 55.8 µs | 17.5 µs |
| FastAPI 0.115.6 | Python | 1,290 | 4,828 | 256 ms | 20× | 54.8 µs | 17.5 µs |
| Hono v4.7.4 | TypeScript | 368 | 992 | 240 ms | 53× | 25.0 µs | 60.5 µs |
| svelte.dev @ 44823b4 | Svelte | 841 | 685 | 240 ms | 41× | 26.9 µs | 17.5 µs |
| Redis 7.4.2 | C | 818 | 14,648 | 344 ms | 133× | 35.7 µs | 17.5 µs |
| LevelDB 1.23 | C++ | 132 | 1,531 | 231 ms | 418× | 38.5 µs | 17.5 µs |
| Gin v1.10.0 | Go | 92 | 1,184 | 235 ms | 125× | 50.5 µs | 17.5 µs |
| Guava v33.4.8 | Java | 3,275 | 56,805 | 975 ms | 112× | 88.5 µs | 17.5 µs |
| Newtonsoft.Json 13.0.3 | C# | 933 | 7,284 | 312 ms | 65× | 22.2 µs | 17.5 µs |
| bats-core v1.11.1 | Bash | 54 | 147 | 222 ms | N/A³ | 21.3 µs | 30.7 µs |
| RuboCop v1.65.0 | Ruby | 1,539 | 9,122 | 290 ms | 61× | 56.1 µs | 17.5 µs |
| SwiftLint 0.57.0 | Swift | 667 | 3,781 | 248 ms | 52× | 36.6 µs | 17.5 µs |
| SDWebImage 5.19.0 | Objective-C | 271 | 1,564 | 237 ms | 54× | 20.8 µs | 17.5 µs |
| Laravel v11.9.2 | PHP | 2,331 | 26,127 | 612 ms | 80× | 66.9 µs | 17.6 µs |
| OpenZeppelin Contracts v5.6.1 | Solidity | 661 | 4,073 | 23.4 ms | 49× | 80.2 µs | 23.0 µs |
| zls 0.13.0 | Zig | 67 | 2,422 | 240 ms | 801× | 51.4 µs | 17.7 µs |
| OkHttp 5.3.2 | Kotlin | 636 | 6,680 | 278 ms | 56× | 52.3 µs | 17.9 µs |
| Roact v1.4.4 | Lua | 95 | 93 | 223 ms | 90× | 21.3 µs | 22.7 µs |
¹ Median of 5 runs. ² Token efficiency is the median ratio of full-file size to symbol size across all class/struct/interface/type-alias symbols — how many times cheaper get_symbol is versus reading the whole file. ³ Bash has no class/struct symbols, only functions, so the metric does not apply.
search_symbolslatencies use the default BM25 mode (tantivy ranked full-text). Measured with Criterion over 100 samples per corpus. BM25 query time remains largely independent of corpus size — 21–89 µs across all 18 repos — because tantivy's inverted index avoids a linear symbol scan. The exact substring path now ranges from faster than BM25 on the tiniest corpora to 61× slower on Guava, because deterministic pagination sorts the full match set before slicing. Fuzzy (trigram) ranges from 4× to 792× slower and remains an explicit opt-in.LevelDB's 418× median reflects C++ class body trimming — inline method bodies are stripped, leaving only the class header. FastAPI's 20× median is lower than most because Pydantic models are large by nature (
Schemaalone is 4.8 KB). svelte.dev's 41× median reflects meaningful symbol extraction from embedded<script>blocks across a large Svelte-heavy monorepo while still excluding template/style sections. Guava's 975 ms index time and 56,805 symbols make it the heaviest corpus by a factor of 4×;get_project_outlineagainst it takes ~13.4 ms vs. sub-1.8 ms for every corpus except Laravel. Laravel'sget_symbollatency of 17.6 µs reflects the benchmark target beingEnumerable— a 36 KB interface that is nearly the entire file it lives in; interface bodies are never trimmed since their signatures are the API contract. OpenZeppelin Contracts lands at a 48.8× median because large Solidity interfaces such asIAccessManagerare intentionally returned at full extent; unlike contracts with executable bodies, interface definitions are not trimmed. zls's 801× median reflects Zig's tendency toward large files with many small struct/enum declarations;src/lsp.zigalone is 347 KB and contains hundreds of compact LSP message types.
Running the benchmarks
Clone the test corpora first (one-time setup):
bash bench/setup.sh
Memory, disk, and token efficiency (single binary, human-readable output):
# All repos
cargo run --release --features memory-bench --bin memory_bench
# One or more repos by name
cargo run --release --features memory-bench --bin memory_bench -- bats
cargo run --release --features memory-bench --bin memory_bench -- ripgrep fastapi
Query latency (Criterion, saves baseline for regression tracking):
# All repos
cargo bench --bench queries
# One repo (Criterion's built-in filter)
cargo bench --bench queries -- bats
cargo bench --bench queries -- "ripgrep|gin"
Indexing throughput (Criterion):
cargo bench --bench indexing
Experimental Features
Semantic Search
pitlane-mcp supports opt-in semantic search powered by locally-running embedding models via Ollama or LM Studio. When enabled, search_symbols gains a "semantic" mode that ranks results by meaning rather than keyword overlap — useful for finding symbols by concept when you don't know their exact names.
See SEMANTIC_SEARCH.md for setup instructions, model recommendations, and known limitations.
Security
pitlane-mcp is a fully local tool with no network calls. The following design properties are intentional but worth understanding before deploying it with AI agents.
Filesystem access scope
By default, index_project, find_usages, and watch_project accept any path accessible to the running process. An AI agent (or a prompt-injected instruction) can call these tools with sensitive directories such as ~/.ssh, ~/.config, or /etc.
To opt into confinement, set PITLANE_ALLOWED_ROOTS to a platform-native path list (:-separated on Unix, ;-separated on Windows). Use fully expanded absolute paths; config values are not shell-expanded. When set, pitlane-mcp rejects project paths outside those roots, and file-level tools reject absolute paths or traversal outside the indexed project root.
Example:
export PITLANE_ALLOWED_ROOTS="/home/alice/src:/home/alice/work"
Mitigating factors:
- Only files with recognized source extensions are indexed or read (
.rs,.py,.js,.ts,.tsx,.c,.cpp,.h,.hpp,.go,.swift,.m,.mm,.php,.zig,.luau,.lua,.sol, etc.). Most sensitive files — SSH keys, certificates,.envfiles — have no matching extension and are silently skipped. - Symbolic links are never followed (
follow_links: falsein all directory walks). - Files larger than 1 MiB are skipped.
Recommendation: If you deploy pitlane-mcp with an AI agent in an environment where prompt injection is a concern, treat it as having read access to any source file the OS user can read.
Resource cap on directory walks
index_project enforces a configurable max_files cap (default: 100,000 source files). If the walk finds more eligible files than the cap, it returns a FILE_LIMIT_EXCEEDED error instead of indexing. This prevents accidental full-filesystem walks (e.g. index_project("/")). Raise max_files explicitly for very large mono-repos.
Index storage
Indexes are stored unencrypted at ~/.pitlane/indexes/{blake3_hash}/. If another local user or process can write to your home directory they could tamper with index files; however, deserialization failures are handled gracefully and will not execute arbitrary code.
License
Licensed under either of MIT License or Apache License, Version 2.0, at your option.