code-analyze-mcp
Native agent tools (regex search, path matching, file reading) handle targeted lookups well.aptu-coder handles the mechanical, non-AI work: mapping directory structure, extracting symbols, and tracing call graphs. Offloading this to a dedicated tool reduces token usage and speeds up coding with better accuracy.
Benchmarks
Auth migration task on Claude Code against Django (Python) source tree. Full methodology.
Mode | Sonnet 4.6 | Haiku 4.5 |
MCP | 112k tokens, $0.39 | 406k tokens, $0.42 |
Native | 276k tokens, $0.95 | 473k tokens, $0.53 |
Savings | 59% fewer tokens, 59% cheaper | 14% fewer tokens, 21% cheaper |
AeroDyn integration audit task on Claude Code against OpenFAST (Fortran) source tree. Full methodology.
Mode | Sonnet 4.6 | Haiku 4.5 |
MCP | 472k tokens, $1.65 | 687k tokens, $0.72 |
Native | 877k tokens, $2.85 | 2162k tokens, $2.21 |
Savings | 46% fewer tokens, 42% cheaper | 68% fewer tokens, 68% cheaper |
Overview
aptu-coder is a Model Context Protocol server that gives AI agents precise structural context about a codebase: directory trees, symbol definitions, and call graphs, without reading raw files. It supports Rust, Python, Go, Java, TypeScript, TSX, Fortran, JavaScript, C/C++, and C#, and integrates with any MCP-compatible orchestrator.
Supported Languages
All languages are enabled by default. Disable individual languages at compile time via Cargo feature flags.
Language | Extensions | Feature flag |
Rust |
|
|
Python |
|
|
TypeScript |
|
|
TSX |
|
|
Go |
|
|
Java |
|
|
Fortran |
|
|
JavaScript |
|
|
C |
|
|
C++ |
|
|
C# |
|
|
To build with a subset of languages, disable default features and opt in:
[dependencies]
aptu-coder-core = { version = "*", default-features = false, features = ["lang-rust", "lang-python"] }The current version is published on crates.io. Replace "*" with the latest version string if you prefer a pinned dependency.
Installation
Homebrew (macOS and Linux)
brew install clouatre-labs/tap/aptu-coderUpdate: brew upgrade aptu-coder
cargo-binstall (no Rust required)
cargo binstall aptu-codercargo install (requires Rust toolchain)
cargo install aptu-coderQuick Start
Build from source
cargo build --releaseThe binary is at target/release/aptu-coder.
Configure MCP Client
After installation via brew or cargo, register with the Claude Code CLI:
claude mcp add --transport stdio aptu-coder -- aptu-coderIf you built from source, use the binary path directly:
claude mcp add --transport stdio aptu-coder -- /path/to/repo/target/release/aptu-coderstdio is intentional: this server runs locally and processes files directly on disk. The low-latency, zero-network-overhead transport matches the use case. Streamable HTTP adds a network hop with no benefit for a local tool.
Or add manually to .mcp.json at your project root (shared with your team via version control):
{
"mcpServers": {
"aptu-coder": {
"command": "aptu-coder",
"args": []
}
}
}Tools
All optional parameters may be omitted. Shared optional parameters for analyze_directory, analyze_file, and analyze_symbol (analyze_module does not support these):
Parameter | Type | Default | Description |
| boolean | auto | Compact output; auto-triggers above 50K chars |
| string | -- | Pagination cursor from a previous response's |
| integer | 100 | Items per page |
| boolean | false | Bypass output size warning |
| boolean | false | true = full output with section headers and imports (Markdown-style headers in |
summary=true and cursor are mutually exclusive. Passing both returns an error.
analyze_directory
Walks a directory tree, counts lines of code, functions, and classes per file. Respects .gitignore rules. Default output is a flat PAGINATED list. Pass verbose=true for FILES / TEST FILES section headers. Pass summary=true for a compact STRUCTURE tree with aggregate counts.
Required: path (string) -- directory to analyze
Additional optional:
max_depth(integer, default unlimited) -- recursion limit; use 2-3 for large monoreposgit_ref(string, optional) -- Restrict analysis to files changed relative to this git ref (branch, tag, or commit SHA). Empty string or unset means no filtering.
analyze_directory path: /path/to/project
analyze_directory path: /path/to/project max_depth: 2
analyze_directory path: /path/to/project summary: true
analyze_directory path: /path/to/project verbose: trueanalyze_file
Extracts functions, classes, and imports from a single file.
Required: path (string) -- file to analyze
Additional optional:
ast_recursion_limit(integer, optional) -- tree-sitter AST traversal depth cap; leave unset for unlimited depth. Minimum value is 1; 0 is treated as unset.fields(array of strings, optional) -- limit output to specific sections. Valid values:"functions","classes","imports". Omit to return all sections. The FILE header (path, line count, section counts) is always emitted regardless. Ignored whensummary=true. When"imports"is listed explicitly, theI:section is rendered regardless of theverboseflag.
analyze_file path: /path/to/file.rs
analyze_file path: /path/to/file.rs page_size: 50
analyze_file path: /path/to/file.rs cursor: eyJvZmZzZXQiOjUwfQ==analyze_module
Extracts a minimal function/import index from a single file. ~75% smaller output than analyze_file. Use when you need function names and line numbers or the import list, without signatures, types, or call graphs. Returns an actionable error if called on a directory path, steering to analyze_directory.
Required: path (string) -- file to analyze
analyze_module path: /path/to/file.rsanalyze_symbol
Builds a call graph for a named symbol across all files in a directory. Uses sentinel values <module> (top-level calls) and <reference> (type references). Functions called >3 times show (•N) notation.
Required:
path(string) -- directory to searchsymbol(string) -- symbol name, case-sensitive exact-match
Additional optional:
follow_depth(integer, default 1) -- call graph traversal depthmax_depth(integer, default unlimited) -- directory recursion limitast_recursion_limit(integer, optional) -- tree-sitter AST traversal depth cap; leave unset for unlimited depth. Minimum value is 1; 0 is treated as unset.impl_only(boolean, optional) -- when true, restrict callers to only those originating from animpl Trait for Typeblock (Rust only). ReturnsINVALID_PARAMSif the path contains no.rsfiles. Emits aFILTER:header showing how many callers were retained out of total.match_mode(string, default exact) -- Symbol lookup strategy:exact: Case-sensitive exact match (default)insensitive: Case-insensitive exact matchprefix: Case-insensitive prefix match; returns an error listing candidates when multiple symbols matchcontains: Case-insensitive substring match; returns an error listing candidates when multiple symbols match All non-exact modes return an error with candidate names when the match is ambiguous; use the listed candidates to refine to a unique match.
import_lookup(boolean, optional) -- When true, find all files in the directory that import the module named bysymbol. Mutually exclusive with call-graph mode.git_ref(string, optional) -- Restrict analysis to files changed relative to this git ref (branch, tag, or commit SHA). Empty string or unset means no filtering.def_use(boolean, optional) -- When true, extract definition and use sites for the symbol. The initial response returns callers and callees as usual and includes a cursor that, when followed, pages throughdef_use_sites(each withkind,symbol,file,line,column,snippet,enclosing_scope).def_use_sitesis empty instructuredContentuntil the client follows that cursor into def-use pagination mode.
The tool also returns structuredContent with typed arrays for programmatic consumption: callers (production callers), test_callers (callers from test files), and callees (direct callees), each as Option<Vec<CallChainEntry>>. A CallChainEntry has three fields: symbol (string), file (string), and line (JSON integer; usize in the Rust API). These arrays represent depth-1 relationships only; follow_depth does not affect them.
Example output:
FOCUS: format_structure_paginated (1 defs, 1 callers, 3 callees)
CALLERS (1-1 of 1):
format_structure_paginated <- analyze_directory
<- format_structure_paginated
CALLEES: 3 (use cursor for callee pagination)analyze_symbol path: /path/to/project symbol: my_function
analyze_symbol path: /path/to/project symbol: my_function follow_depth: 3
analyze_symbol path: /path/to/project symbol: my_function max_depth: 3 follow_depth: 2analyze_raw
Read a file or range of lines from a file. Returns the file content with line numbers. Specify start_line and end_line (1-indexed, inclusive) to read a range; omit for full file.
Required: path (string) -- file to read
Additional optional:
start_line(integer, optional) -- starting line number (1-indexed, inclusive). Defaults to 1 if omitted.end_line(integer, optional) -- ending line number (1-indexed, inclusive). Defaults to the last line if omitted.
analyze_raw path: /path/to/file.rs
analyze_raw path: /path/to/file.rs start_line: 1 end_line: 50
analyze_raw path: /path/to/file.rs start_line: 100 end_line: 150edit_overwrite
Create or overwrite a file at path with content. Creates parent directories if needed. Overwrites without confirmation; use edit_replace to replace a specific block instead of the whole file.
Required:
path(string) -- file to create or overwritecontent(string) -- UTF-8 content to write
edit_overwrite path: tests/foo_test.rs content: "..."
edit_overwrite path: src/config.rs content: "..."edit_replace
Replace a unique exact text block in a file. Errors if old_text appears zero times or more than once; fix by making old_text longer and more specific. Use edit_overwrite to replace the whole file.
Required:
path(string) -- file to editold_text(string) -- exact text block to find and replace (must appear exactly once)new_text(string) -- replacement text
edit_replace path: src/main.rs old_text: "..." new_text: "..."edit_rename
AST-aware rename within a single file. Matches only syntactic identifiers -- identifiers in string literals and comments are excluded. Errors if old_name not found. Note: the kind parameter is reserved for future use; supplying it currently returns an error.
Required:
path(string) -- file to modifyold_name(string) -- current name of the symbol (identifier) to renamenew_name(string) -- new name for the symbol
Additional optional: kind (string, optional) -- reserved for future use; currently returns an error if supplied.
edit_rename path: src/config.rs old_name: parse_config new_name: load_config
edit_rename path: src/client.rs old_name: timeout new_name: timeout_msedit_insert
Insert content immediately before or after a named AST node. position is before or after. The caller is responsible for including necessary newlines in content. Uses the first occurrence if symbol_name appears multiple times.
Required:
path(string) -- file to modifysymbol_name(string) -- name of the symbol (identifier) to locateposition(string) --beforeoraftercontent(string) -- content to insert verbatim; include leading/trailing newlines as needed
edit_insert path: src/lib.rs symbol_name: handle_request position: before content: "#[instrument]\n"
edit_insert path: src/types.rs symbol_name: MyStruct position: after content: "\n#[derive(Debug)]\n"Output Management
For large codebases, two mechanisms prevent context overflow:
Pagination
analyze_file and analyze_symbol append a NEXT_CURSOR: line when output is truncated. Pass the token back as cursor to fetch the next page. summary=true and cursor are mutually exclusive; passing both returns an error.
# Response ends with:
NEXT_CURSOR: eyJvZmZzZXQiOjUwfQ==
# Fetch next page:
analyze_symbol path: /my/project symbol: my_function cursor: eyJvZmZzZXQiOjUwfQ==Summary Mode
When output exceeds 50K chars, the server auto-compacts results using aggregate statistics. Override with summary: true (force compact) or summary: false (disable).
# Force summary for large project
analyze_directory path: /huge/codebase summary: true
# Disable summary (get full details, may be large)
analyze_directory path: /project summary: falseNon-Interactive Pipelines
In single-pass subagent sessions, prompt caches are written but never reused. Benchmarks showed MCP responses writing ~2x more to cache than native-only workflows, adding cost with no quality gain. Set DISABLE_PROMPT_CACHING=1 (or DISABLE_PROMPT_CACHING_HAIKU=1 for Haiku-specific pipelines) to avoid this overhead.
The server's own instructions expose a 4-step recommended workflow for unknown repositories: survey the repo root with analyze_directory at max_depth=2, drill into the source package, run analyze_module on key files for a function/import index (or analyze_file when signatures and types are needed), then use analyze_symbol to trace call graphs. MCP clients that surface server instructions will present this workflow automatically to the agent.
Environment Variables
Variable | Default | Description |
|
| Maximum number of file-analysis results held in the in-process LRU cache. Increase for large repos where many files are queried repeatedly. |
|
| Maximum number of directory-analysis results held in the in-process LRU cache. |
| unset | Set to |
| unset | Set to |
Observability
All nine tools emit metrics to daily-rotated JSONL files at $XDG_DATA_HOME/aptu-coder/ (fallback: ~/.local/share/aptu-coder/). Each record captures tool name, duration, output size, and result status. Files are retained for 30 days. See docs/OBSERVABILITY.md for the full schema.
Documentation
ARCHITECTURE.md - Design goals, module map, data flow, language handler system, caching strategy
MCP Best Practices - Best practices for agentic loops, orchestration patterns, MCP tool design, memory management, and safety controls
OBSERVABILITY.md - Metrics schema, JSONL format, and retention policy
ROADMAP.md - Development history and future direction
DESIGN-GUIDE.md - Design decisions, rationale, and replication guide for building high-performance MCP servers
CONTRIBUTING.md - Development workflow, commit conventions, PR checklist
SECURITY.md - Security policy and vulnerability reporting
License
Apache-2.0. See LICENSE for details.
This server cannot be installed
Maintenance
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/clouatre-labs/aptu-coder'
If you have feedback or need assistance with the MCP directory API, please join our Discord server