ast-mcp-server
Correctness-oriented MCP server and batch CLI for compact structural reads and reviewed TypeScript/JavaScript edits.
Documentation
ast-mcp-server
`ast-mcp-server` gives coding agents compact, type-aware access to TypeScript and JavaScript projects. It uses the real compiler project model through `ts-morph`, so declarations, references, rename locations, and diagnostics come from the AST instead of text-search guesses.
Reads are bounded and structured. Writes follow an explicit `prepare → review → apply` protocol with immutable hashes, workspace freshness checks, diagnostic guards, and idempotent receipts.
The problem
Coding agents often fall back to two generic operations: read files as plain text and write text patches. That works, but it has three predictable costs:
1. Too much context. The agent may load hundreds of lines when it only needs one signature or method body. That consumes model context and tokens without improving the answer.
2. Fragile edits. Text patches do not inherently understand declarations, scopes, overloads, or TypeScript diagnostics. A plausible-looking edit can target the wrong construct or introduce a new compiler error.
3. Weak cross-file reasoning. Text search can find matching words, but it cannot reliably distinguish two unrelated symbols with the same name. Project-wide references and renames need the compiler's understanding of the program.
What this tool does instead
This MCP server gives the agent structural code tools in addition to generic file reads and writes. Under the hood, `ts-morph` uses the TypeScript compiler project model, so the server can reason about declarations and references as code rather than undifferentiated text.
| Need | Structural operation | Returned scope |
|---|---|---|
| Read a bounded file | `ast_get_file` | Exact selected source lines, hashes, and bounded freshness |
| Explore bounded context | `ast_explore` | Ranked selectors plus optional source and references |
| Understand a file | `ast_get_outline` | Signatures without implementation bodies |
| Inspect one declaration | `ast_get_symbol_source` | Exact source for one function, method, class, or type |
| Find usages across the project | `ast_find_references` | Compiler-resolved reference locations |
| Understand symbol impact | `ast_get_impact` | Bounded direct/transitive compiler-backed relationships |
| Select affected tests | `ast_find_test_candidates` | Whole candidate proofs from incoming compiler relationships |
| Rename a symbol everywhere | `ast_rename_symbol` | A reviewed project-wide rename plan |
| Change one implementation | `ast_replace_symbol_body` | A body-only plan that preserves the declaration |
Reads can start with a bounded file slice, a compact outline, or exact source only for the declaration that needs inspection. Mutations are prepared in memory first, compared against baseline diagnostics, and returned as immutable, hash-bound plans. Nothing is written until the caller reviews and explicitly applies the plan.
Choosing a read tool
- Use `ast_get_file` when the file path is known and the agent needs exact source lines. It is read-only, uses zero-based `offset` and bounded `limit`, returns one-based line records, a SHA-256 byte hash, file-level `snapshot_state`, and bounded project `freshness` metadata (`fresh`, `pending`, `stale`, `rebuilding`, or `degraded`).
- Use `ast_get_file` with `symbols_only: true` when only selectors and body-free signatures are needed from one known file.
- Use `ast_explore` when the question spans discovery and evidence. Its default summary is bounded; use `detail: "context"` for selected source and `detail: "full"` for source plus compiler references.
- Use `ast_get_outline` for a compact body-free view of a known file without source lines.
- Use `ast_get_symbol_source` when one declaration or implementation is the required evidence.
- Use `ast_get_impact` when the exact symbol is known and bounded direct/transitive compiler relationships are needed; it is read-only evidence, not a mutation plan.
- Use `ast_find_test_candidates` when an exact symbol should map to conservative test candidates. It forces incoming compiler traversal, returns complete relationship paths, and never executes tests.
`snapshot_state: "fresh"` means that the returned file bytes match the synchronized compiler snapshot. The separate `freshness` object describes the project/session state and preserves causes such as source changes or watcher failure. Neither field means that the project has zero TypeScript diagnostics; use `ast_get_diagnostics` for compiler errors and warnings.
Trust, freshness and completeness
The server exposes evidence labels instead of collapsing every result into an unqualified confidence score:
| Label | Meaning | Safe use |
|---|---|---|
| `provenance: "compiler"`, `confidence: "exact"`, `resolution: "resolved"`, `freshness.state: "fresh"` | A relationship resolved by the active TypeScript compiler snapshot. This is the only combination that sets `compiler_authoritative: true`. | May support bounded impact evidence and compiler-backed test candidates. |
| `provenance: "syntax"` | Syntax or AST structure without semantic symbol resolution. | Navigation and structural context only; not proof that two symbols are related. |
| `provenance: "heuristic"` | A convention or name-based suggestion. | Discovery hints only; never mutation authority or a compiler-backed test candidate. |
| index evidence | A derived query accelerator, not compiler authority. The production default uses native SQLite when persistence is absent or `enabled`; every indexed selector still requires compiler validation and a compiler fallback. | Faster routing only; stale, missing or mismatched entries must fail closed or fall back to the compiler. |
Freshness is orthogonal to TypeScript diagnostics. `fresh` means the evidence matches the synchronized snapshot; `pending`, `rebuilding`, `stale`, or `degraded` means the response must not be presented as current compiler evidence. Read tools expose the state, causes (`source_change`, `config_change`, `index_failure`, `watcher_failure`, or `compiler_rebuild`), and bounded `checked_at` timestamp. `ast_get_impact` refuses non-fresh compiler relationships. `ast_explore` returns the state together with `completeness`, `unresolved`, `budget`, and `truncation` metadata rather than silently dropping evidence.
All reads are budgeted. Callers control pagination and, where applicable, `max_bytes`, `reference_limit`, `max_depth`, `max_nodes`, and `max_edges`; responses report the effective limits and whether a record, byte, depth, edge, invocation, or serialization limit truncated the result. A truncated or unresolved result is incomplete evidence, not an empty negative result. `ast_find_test_candidates` follows the same rule: it accepts only fresh, exact compiler-backed impact, emits direct/transitive evidence and bounded relationship IDs, and never executes tests or guesses from filenames alone. Only a complete authoritative traversal may return `candidates: []` with `proven_empty: true`.
Why this helps
- Less context: the agent retrieves the smallest structural unit that answers the question instead of loading the complete file by default.
- Safer changes: exact symbol selection, diagnostic deltas, workspace freshness checks, and `prepare → review → apply` reduce the failure modes of ad hoc text editing.
- Accurate project-wide operations: references and renames use compiler resolution rather than matching identifier text with grep.
AST-aware editing is not a proof that a change is semantically correct. The safety comes from combining structural selection with diagnostics, exact previews, reviewed hashes, freshness checks, and fail-closed apply semantics.
The included batch benchmark records a 50% reduction in model round-trips and a 94.67% reduction in serialized context for its search-to-source scenario. The result-shaping corpus records a 68.80% reduction in aggregate model-facing TOON tokens while preserving declared selectors/reference coordinates with the same six logical calls. The separate format benchmark records 25.87% across its eligible collection corpus. The context workflow benchmark verifies evidence preservation and call bounds for full-file, primitive, and `ast_explore` workflows. These are reproducible local `o200k_base` estimates, not universal token, billing, cache, or latency claims.
Requirements
- Node.js 22.13.0 or newer
- Corepack with Yarn 4.15.0 (pinned by `packageManager`)
- A target project with a `tsconfig.json`
Supported environment and trust boundary
Published v0.12.0 requires Node.js `>=22.13.0`; its immutable evidence matrix targets exact Node.js 22.13.0 and the current Node.js 24 line. Structural apply and managed setup-file publication are verified only on Linux x64 with GNU coreutils 9.7 `mv` supporting `--update=none-fail`, `--exchange`, `--no-copy`, and `--no-target-directory`, GNU coreutils `ln -L -T`, procfs descriptor paths at `/proc/self/fd`, `O_DIRECTORY`/`O_NOFOLLOW`, and a destination filesystem that passes the owned link/exchange identity probe. A failed or denied primitive blocks mutation before source effects; there is no rename, copy/delete, or pathname-only fallback. Other Linux architectures or systems without this complete matrix, macOS, and Windows remain unverified.
This is a local stdio server. It runs with the invoking user's filesystem permissions, and clients may request any `project_root` that user can access. It does not provide HTTP authentication, sandboxing, tenant isolation, or a remote-service security boundary. Remote, untrusted, and multi-tenant operation is unsupported.
Optional supervised compiler worker
The compiler runs in process by default. Linux operators may explicitly keep the stdio parent connected while allowing an idle compiler child to exit and lazily respawn:
AST_COMPILER_WORKER_MODE=supervised ast-mcp-serverThe parent waits for child readiness before replaying bounded initialization state. Requests and cancellation remain generation-affine; mutation history, live operation leases, and completion-critical apply work prevent unsafe recycling. Set `AST_COMPILER_WORKER_MODE=in_process` for the full rollback, or set `AST_COMPILER_WORKER_IDLE_TTL_MS=0` to retain the relay while disabling idle recycling.
The scoped Linux canary passed on exact Node.js 22.13.0 and Node.js 24 with repeatable PSS reclamation, stable compiler fingerprints, unchanged SQLite reuse, bounded redacted diagnostics, and no orphan after parent death. This is one child per connection, not a shared daemon, pool, or new default. See ADR 0014.
In published v0.12.0, an absent `AST_SYMBOL_INDEX_PERSISTENCE` or explicit `enabled` selects the private SQLite symbol-index cache. `disabled` is the immediate memory-only rollback. `canary` requires an explicit absolute normalized `AST_SYMBOL_INDEX_CACHE_ROOT`. Invalid policy or storage fails closed to compiler-authoritative memory reads with bounded path-free status.
The default cache root is selected from `AST_SYMBOL_INDEX_CACHE_ROOT`, then `XDG_CACHE_HOME`, then `HOME`. Inspect or clear only derived cache artifacts through the bounded CLI:
ast-tool cache inspect
ast-tool cache clear --yesClear requires exact confirmation, refuses unsafe or active SQLite artifacts, and preserves unknown regular files. No automatic cache GC is enabled.
See Support policy for the complete platform, runtime, persistence, and operational contract. Report security issues through SECURITY.md.
Install
Install the published CLI globally while keeping dependency lifecycle scripts disabled:
npm install --global ast-mcp-server --ignore-scripts
ast-tool setup`--ignore-scripts` prevents dependencies from running `preinstall`, `install`, or `postinstall` hooks. The package and its current runtime dependencies do not require those hooks.
Install from source
To build the current source instead:
git clone https://github.com/yailPeralta/ast-mcp-server.git
cd ast-mcp-server
corepack enable
yarn install --immutable
yarn buildThe repository pins Yarn 4 and commits `enableScripts: false` in `.yarnrc.yml`. Dependency lifecycle scripts are therefore disabled during installation; switching from npm without this setting would merely change logos while preserving the risk.
The package exposes two executables when installed:
- `ast-mcp-server`: MCP stdio server.
- `ast-tool`: batch, skill-installation, and agent-setup CLI.
Diagnose the active installation
ast-tool doctor [--project ]Doctor reuses CLI project discovery and existing runtime authorities without changing project,
agent, package, or skill state. It prints bounded JSON; exit `0` is healthy, `1` degraded, and `2`
failed. A healthy compiler remains usable when only the derived SQLite index is degraded.
Standalone diagnosis marks registered-session-only index and queue evidence as `not_run` rather
than fabricating healthy state.
Upgrade an installed package
Inspect the active global installation without writing, or update it immediately:
ast-tool upgrade --check
ast-tool upgradeUpgrade supports only a direct package proven to belong to the active npm global prefix or to Volta. It rejects linked/source and ambiguous installations, never uses `sudo`, guesses from `PATH`, or accepts `--yes`. npm runs its proven `npm-cli.js` through the active Node with lifecycle scripts disabled and a disposable cache/config copy; cleanup is verified and blocks success if absence cannot be proven. Volta uses its native integration. The updated CLI then reconciles managed setup through that Node. Customized skill bytes remain untouched and produce `ast-tool setup --agents all --yes --force-skill`. Restart MCP clients after any package update; an already-running server does not change in place.
Guided agent setup
The installed package opens the interactive wizard with:
ast-tool setupFrom a source checkout, use the Yarn script; it builds first and then opens the same wizard:
yarn setupThe wizard supports exactly six CLI clients in this order: Claude Code, Hermes, OpenCode, Codex CLI, Gemini CLI, and GitHub Copilot CLI. Cursor, Windsurf, Cline, and other editor-integrated clients are intentionally excluded. Compatible detected clients start checked; unavailable or incompatible clients are disabled with a reason. Use Up/Down to move, Space to toggle, Enter to submit, or Escape/Ctrl-C to cancel.
1. preflights every selected client's existing `ast` MCP registration, skill destination, and effective managed-guidance destination;
2. installs or safely upgrades the bundled `structural-code-editing` skill;
3. adds one marker-owned activation block to each verified global instruction surface while preserving all user-owned bytes;
4. registers this package's MCP server through the agent's official CLI;
5. reconnects and verifies the expected tools.
Existing matching registrations, skill files, and managed blocks are unchanged. Conflicting MCP registrations or malformed/unknown managed guidance fail before any write; resolve them explicitly instead of letting a setup script guess. Skill upgrades are automatic only when the installed bytes match an exact SHA-256 admitted from a published npm tarball. Unknown or customized skill bytes fail closed unless `--force-skill` is explicit. That flag applies only to the skill and cannot override guidance conflicts, unsafe routes, or filesystem races.
Guidance uses each client's verified global instruction contract rather than one universal filename:
| Client | Managed guidance destination |
|---|---|
| Claude | `$CLAUDE_CONFIG_DIR/CLAUDE.md`, or `~/.claude/CLAUDE.md` |
| OpenCode | Effective native `AGENTS.md`; an existing Claude fallback may be shared or preserved when OpenCode has no native file |
| Codex | Non-empty `$CODEX_HOME/AGENTS.override.md`, otherwise `$CODEX_HOME/AGENTS.md`; `CODEX_HOME` defaults to `~/.codex` |
| Gemini | The one supported safe `context.fileName` from `~/.gemini/settings.json`, otherwise `~/.gemini/GEMINI.md` |
| Hermes | `skill_only`; setup does not modify `SOUL.md` or invent a global instruction destination |
| Copilot | `skill_only`; setup does not invent a personal global instruction destination |
The managed range is delimited by `ast-tool:structural-code-editing guidance v1` begin/end markers. Setup updates only that range, preserves the file's UTF-8 BOM, newline style, mode, and all content outside the range, and rejects duplicate, partial, reordered, unknown, symlinked, or non-regular destinations. Writes pin the parent chain, preimage, held temporary inode, and destination. New files use descriptor-bound no-clobber publication; replacements use an atomic same-directory exchange, validate both exchanged identities plus the pinned preimage bytes and mode, and roll the exact pair back when an in-call substitution or same-inode edit is detected. Every completed postimage is reauthenticated before later asset or MCP mutation. Cross-client setup is convergent rather than globally transactional.
Successful setup output uses schema `version: 2`. Each agent reports `mcp`, `skill`, and `guidance`; physical writes include an `asset` of `skill`, `guidance`, or `mcp_config`. A complete replay returns every applicable item as `unchanged`/`skill_only` and an empty `physical_writes` array. A failed managed publication separates `completed_writes`, `possibly_committed`, `rolled_back`, `rollback_failed`, and `pending`; an uncertain commit or failed rollback is never reported as untouched and requires inspection plus a fresh replan.
For automation, make the target set and confirmation explicit:
ast-tool setup --agents all --yes
ast-tool setup --agents claude,codex --yesFrom a source checkout, replace `ast-tool` with `yarn setup` in those commands.
`--agents all` is resolved only after detection and means every detected compatible client. If any detected client has unknown or incompatible output, setup fails before writes. Explicit IDs are strict and reject unavailable clients. Non-interactive setup requires both `--agents` and `--yes`.
OpenCode 1.18.18 or newer is required. Because `opencode mcp add` ignores custom config routing, setup updates only `mcp.ast` in `OPENCODE_CONFIG`, then `OPENCODE_CONFIG_DIR/opencode.json`, then `~/.config/opencode/opencode.json`. JSONC comments, unrelated keys, and file mode are preserved. OpenCode's nominally diagnostic config command normalizes both routed config files, so setup runs discovery and verification against disposable copies while retaining the selected config bytes and fails closed if the planned real destination changes. Gemini setup may require trusting the current folder before registration. Diagnostics use a correlation ID and omit command arguments, environment, credentials, and raw provider output; setup failures may include a bounded destination path so the operator can inspect an uncertain or pending write.
Install the agent skill
The package bundles a `structural-code-editing` skill that teaches an agent when to use the AST tools, how to minimize context, and how to review mutations safely. Install it for both Claude Code and Hermes with one command:
ast-tool install-skill allOr install one target at a time:
ast-tool install-skill claude
ast-tool install-skill hermesThe default is user scope. It writes to Claude Code's personal skill directory and to the active `HERMES_HOME`:
| Target | Destination |
|---|---|
| Claude Code | `$CLAUDE_CONFIG_DIR/skills/structural-code-editing/SKILL.md`, or `~/.claude/skills/...` by default |
| Hermes | `$HERMES_HOME/skills/software-development/structural-code-editing/SKILL.md`, or `~/.hermes/...` by default |
| OpenCode, Codex, Gemini, Copilot | `~/.agents/skills/structural-code-editing/SKILL.md` (one physical write, four logical outcomes) |
To commit the skill into one project for Claude Code, use project scope:
ast-tool install-skill claude --scope project --project-root /absolute/projectThis writes `.claude/skills/structural-code-editing/SKILL.md` below that project. Project scope is intentionally rejected for Hermes because Hermes skills belong to a profile, not a source repository.
Installation is idempotent. Existing current bytes are left untouched; exact predecessor bytes admitted by the bundled npm-provenance manifest are upgraded safely. Unknown or customized bytes fail closed unless `--force` is explicit. `install-skill` never writes global guidance or configures MCP. From an unlinked source checkout, replace `ast-tool` with `yarn node /absolute/path/to/ast-mcp-server/dist/cli.js`.
Claude Code detects changes in an existing skill directory live; restart it if the top-level skills directory did not exist when the session started. In Hermes, run `/reload-skills` or start a new session, then verify with `hermes skills list`.
`install-skill` only installs the skill; it does not configure the MCP transport. Use the guided `setup` command to do both, or complete the client-specific MCP setup below—the instructions are useful, but they have not yet learned to open a stdio socket through positive thinking.
Use with Claude Code
Claude Code supports local stdio MCP servers. After building this repository, register the server with an absolute entrypoint:
AST_MCP_DIR="$(pwd)"
claude mcp add --scope user --env AST_MCP_APPLY_GUARD=allow --transport stdio ast -- \
node "$AST_MCP_DIR/dist/index.js"
claude mcp get ast`claude mcp get ast` should report `Status: ✔ Connected`. The `--` separator is required: everything after it is the server command, not a Claude Code option.
The example uses `--scope user`, which makes the server available in all your projects. Use `--scope local` instead to register it only for the project from which you run the command. Avoid committing a project-scoped `.mcp.json` that contains another developer's absolute checkout path.
Start Claude Code inside any TypeScript project with a `tsconfig.json`:
cd /absolute/path/to/your-typescript-project
claudeThen ask Claude to use the `ast` tools. For example:
Use the ast MCP server to inspect this project.
First search for UserService, then fetch only the exact source of its create method.For a reviewed rename:
Use ast_rename_symbol to prepare renaming UserService.create to createUser.
Do not apply it yet. Show me the affected files, diagnostic delta, plan hash,
and the complete operation preview.After reviewing the preview:
Apply that operation with ast_apply_operation using the exact operation_id and
plan_hash returned by the prepare step.Project-scoped read and prepare tools require `project_root`. Claude should pass the current project directory or its explicit `tsconfig.json` path. Preview and apply calls instead use the prepared operation coordinates; the MCP server itself contains no repository-specific paths.
MCP or batch CLI?
| Workflow | Recommended interface |
|---|---|
| Interactive exploration or one reviewed mutation | Claude Code MCP tools |
| A known multi-step read pipeline | `ast-tool run pipeline.json` through Bash |
| Prepare now and apply in a later process | `ast-tool run`, then `ast-tool apply` |
Use `/mcp` inside Claude Code to inspect server status and tools. Outside the session, use `claude mcp list`, `claude mcp get ast`, or `claude mcp remove ast -s user`.
Other MCP clients
Hermes Agent:
hermes mcp add ast --command node --env AST_MCP_APPLY_GUARD=allow --args /absolute/path/to/ast-mcp-server/dist/index.js
hermes mcp test astProject-scoped tools accept `project_root`, either the project directory or an explicit `tsconfig.json` path. The server contains no repository-specific paths.
DeepSeek Harness (Developer Preview)
A thin adapter ships inside this package: `cordis.patch.yml` mounts the packaged
`ast-mcp-server` stdio command through the official
bridge, declared through exactly `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`.
This is a Developer Preview against a pinned, source-built Harness revision
(`dsh-v0.1.2-alpha.1` at `cd5ef8148158c3a752a658978873241fdf8e2bbc`). The published
`ast-mcp-server@0.13.0` package is the compatibility baseline:
dsh plugin --profile web add ast-mcp-server@0.13.0For a local candidate, pack and install its tarball instead:
yarn pack --out ast-mcp-server-%v.tgz
dsh plugin --profile web add ./ast-mcp-server-0.13.1.tgzThe first supported surface is reads + prepare + preview. Every apply path is
denied by a fail-closed guard: `ast_apply_operation` is not registered unless
`AST_MCP_APPLY_GUARD=allow` is set explicitly (the shipped patch instead pins `deny`,
so the Harness surface stays deny-by-default; an unset or invalid value also denies).
The adapter also sets `AST_MCP_TEXT_PROJECTION=canonical_json`: successful structured
results keep their lossless `structuredContent` and gain canonical JSON text only when
ordinary MCP text is empty, because the pinned native presenter otherwise exposes only a
non-useful empty-result marker. The projection is adapter-specific, never replaces existing
text, and reports an explicit size-limit marker when the complete supervised frame has room.
If even the unchanged structured-only result exceeds the existing worker frame, supervised mode
fails closed rather than truncating or corrupting it. Known upstream gaps remain non-authoritative:
the official bridge drops MCP tool annotations
(`readOnlyHint`/`destructiveHint`) and launches the stdio child outside the Harness sandbox.
`yarn test:dsh-adapter` is the mandatory verification. It binds the public 0.13.0 npm
integrity, packs the candidate, builds the pinned Harness and bridge from source, and proves
`tools.mode: native`, the 15-tool scoped catalog, complete executable input schemas,
read/prepare/preview, all three invalid `ast_explore` combinations, apply absence, and rejected direct apply.
The schema gate freezes the public empty `ast_explore` contract as RED, requires the candidate
registry/native definitions to match, and hashes all 14 unaffected model schemas. A deterministic
two-step model then invokes
`mcp__ast__ast_get_project_status` through a real headless Agent/Session: the public baseline
must reproduce the empty-result marker, while the corrected candidate must deliver lossless
canonical JSON to the next model request, the durable `tool/result`, and a cold Agent
resume/replay reconstructed from persistence. The smoke removes and read-backs its disposable
profile/workspace state and fails
(never skips) on an identity mismatch, missing prerequisite, lifecycle leak, or evidence gap.
MCP tools
| Tool | Purpose | Mutates files |
|---|---|---|
| `ast_list_files` | Paginated, project-relative source file inventory | No |
| `ast_get_project_status` | Read-only compiler, freshness, index, and operation status | No |
| `ast_explore` | Bounded composed selectors, source evidence, and references | No |
| `ast_get_file` | Bounded exact source lines, byte hash, and snapshot state | No |
| `ast_get_outline` | Body-free declaration signatures; detailed symbol metadata is opt-in | No |
| `ast_get_symbol_source` | Exact source for one declaration | No |
| `ast_search_symbols` | Paginated structural symbol discovery | No |
| `ast_find_references` | Compiler-resolved references with bounded context | No |
| `ast_get_impact` | Bounded incoming/outgoing compiler-backed impact evidence | No |
| `ast_find_test_candidates` | Paginated affected tests with atomic compiler relationship proofs | No |
| `ast_get_diagnostics` | Project- or file-scoped TypeScript diagnostics | No |
| `ast_rename_symbol` | Prepare a project-wide rename | No |
| `ast_replace_symbol_body` | Prepare a body-only replacement while preserving the signature | No |
| `ast_scaffold_class` | Prepare one new class file with explicit placeholder methods | No |
| `ast_get_operation_preview` | Retrieve the complete retained diff for a prepared plan | No |
| `ast_apply_operation` | Apply one reviewed, hash-bound plan | Yes |
Read results use project-relative paths, deterministic ordering, structured MCP output, and pagination where result sets can grow with the project.
Diagnostic aggregates
Set `include_aggregates: true` on `ast_get_diagnostics` to summarize the complete normalized diagnostic snapshot independently of the selected raw page. The option defaults to `false`; disabled responses omit `aggregates` and keep the existing shape.
Each code and file dimension returns at most 20 ranked groups. `groups.length + omitted_group_count = total_group_count`, and `sum(groups[*].count) = covered_diagnostic_count`. Code coverage plus `omitted_diagnostic_count` equals the diagnostic total; file coverage also adds `unfiled_diagnostic_count`. File groups contain normalized project-relative paths only, but may disclose a path that is absent from the selected raw page.
`ast_explore` supports query, exact file, and exact symbol routes. Its default `summary` profile returns bounded reusable selectors; `context` adds selected source and `full` adds compiler references. Whole symbol clusters are admitted under the caller's `max_bytes` ceiling, so source, reference records, and call paths are never sliced. `omissions` classifies withheld components as `budget`, `incomplete`, or `untrusted`, and any requested omission keeps completeness false.
Exact `file_path` plus `symbol_path` requests may opt into bounded static `call_spines`. Only fresh, exact, compiler-resolved invocation sites qualify; generic references, dynamic dispatch, and runtime behavior are not inferred. Absence of `call_spines` performs no call traversal. Every response still reports freshness, completeness, truncation, unresolved selectors, record limits, and canonical serialized-byte accounting. Use the primitive tools when a single exact operation is clearer or when preparing a mutation. See ADR 0013.
Symbol search is relevance-ranked and defaults to at most 20 `summary` records containing `file`, a directly reusable `selector`, `kind`, and body-free `signature`. Request `detail: "selectors"` for routing coordinates only, or `detail: "full", limit: 100` for the v0.4.0 fields/page. References default to `detail: "locations"`; request `detail: "context"` only when the bounded source line is needed.
Optional TOON results
`ast_search_symbols`, `ast_find_references`, `ast_get_impact`, and `ast_get_diagnostics` accept `output_format: "toon"` for collection-heavy results consumed directly by a model. JSON remains the default and preserves the canonical structured object.
MCP TOON is returned once as structured content shaped like `{ "format": "toon", "data": "..." }`; `data` is the lossless TOON document. The complete JSON result is not duplicated. These four tools validate their canonical Zod result and verify an encode/decode deep-equality round trip before presentation, but do not advertise a single MCP `outputSchema` because their successful structured content has two representations.
Do not request TOON for source, outlines, file lists, previews, or mutation results. Checked negative controls show that the MCP envelope makes those shapes larger. TOON is an explicit shape-specific optimization, not a new dialect for every object in sight.
Batch CLI
`ast-tool` lets Claude Code and other Bash-capable clients collapse a known structural pipeline into one shell call:
ast-tool validate pipeline.json
ast-tool run pipeline.json
ast-tool run pipeline.json --output-format toon
cat pipeline.json | ast-tool run -Example search-to-source pipeline:
{
"version": 1,
"project_root": "/absolute/project",
"steps": [
{
"id": "search",
"tool": "ast_search_symbols",
"input": { "query": "UserService", "limit": 20 }
},
{
"id": "source",
"tool": "ast_get_symbol_source",
"input": {
"file_path": { "$ref": "#/steps/search/symbols/0/file" },
"symbol_path": { "$ref": "#/steps/search/symbols/0/selector" }
}
}
],
"emit": { "$ref": "#/steps/source" }
}A `$ref` is an RFC 6901 JSON Pointer rooted at prior step results. References cannot point forward. If `emit` is omitted, only the final step result is returned; intermediate results remain inside the process.
For CLI batches only, omit `project_root` or provide a directory to select the nearest `tsconfig.json` or `jsconfig.json` from the invocation directory upward. An explicit config file always wins. Discovery stops at a `.git` or filesystem boundary and rejects same-level ambiguity or symlinked identities; MCP tool calls still require an explicit `project_root`.
`ast_find_test_candidates` and `ast_explore` are admitted as read steps. The batch runner injects the pipeline `project_root`, rejects a conflicting step root, and invokes the same registered MCP implementation. Candidate relationship proofs and exploration clusters remain whole; final JSON and TOON differ only in serialization, not logical evidence.
Bounded foreach
{
"version": 1,
"project_root": "/absolute/project",
"limits": { "concurrency": 4 },
"steps": [
{ "id": "files", "tool": "ast_list_files", "input": { "limit": 20 } },
{
"id": "outlines",
"tool": "ast_get_outline",
"foreach": { "$ref": "#/steps/files/files" },
"input": { "file_path": { "$item": "" } }
}
]
}`$item` accepts an empty pointer for the complete item or `/field` for one field. Foreach is read-only, order-preserving, fail-fast, and concurrency-bounded.
Batch limits
- Input document: 1 MiB.
- Steps: 50.
- Total tool invocations: 500.
- Foreach items per step: 200.
- Read concurrency: default 4, maximum 16.
- Each retained step result and final serialized output: 10 MiB.
- Total retained intermediate context: 50 MiB.
- One project root per pipeline.
- No branches, eval, embedded JavaScript, while loops, or arbitrary transformations.
Success is one compact JSON value on stdout by default. `ast-tool run --output-format toon` writes one plain TOON document for a read-only batch; internal steps remain structured JSON, and prepare batches reject TOON before execution. Encoding and output-limit failures write no partial stdout and use stable `ENCODING_ERROR` or `OUTPUT_LIMIT` codes. Errors are always structured JSON on stderr. Exit code 0 is success, 1 is execution/apply failure, and 2 is usage or schema failure.
Reviewed mutations
MCP process
Rename, body replacement, and class scaffold never write directly during preparation:
1. Call `ast_rename_symbol`, `ast_replace_symbol_body`, or `ast_scaffold_class`.
2. Review the diagnostic delta, affected files, `blocked`, and `plan_hash`.
3. Fetch complete diffs with `ast_get_operation_preview` when needed.
4. Call `ast_apply_operation` with both `operation_id` and `plan_hash`.
MCP plans live in a bounded in-memory store and do not survive a server restart.
`ast_scaffold_class` accepts structured imports, heritage, decorators, constructor parameter properties, initialized properties, and one or more method signatures. It creates an in-memory preview for one absent project-relative `.ts`/`.tsx` target. Each generated method initially contains only `throw new Error("Not implemented: Class.method")`. Review the `/dev/null` creation diff and diagnostics, apply the scaffold, then replace each pending method body with `ast_replace_symbol_body`. Existing targets and symbolic/traversing parents fail closed.
CLI process boundary
A batch may contain at most one prepare operation. It must be the final step and cannot use foreach. `ast_apply_operation` and arbitrary preview calls are forbidden inside batch documents.
A CLI prepare writes an exact private plan and returns top-level `operation_id`, `plan_hash`, and `plan_file` even when `emit` omits them:
ast-tool run prepare-rename.json
ast-tool apply /path/from/plan_file.astplan --plan-hashThe default plan directory is:
${XDG_STATE_HOME:-~/.local/state}/ast-tool/plansSet `AST_TOOL_STATE_DIR` to isolate it. Directories are mode `0700`; plans are mode `0600`, atomically replaced, size-bounded, versioned, and expire with the prepared operation. Plan files contain exact proposed source bytes and must be treated as private code.
Apply loads the exact retained postimages, requires the separately supplied reviewed hash, validates serialized byte hashes and contained paths, rechecks the complete source/config workspace, stages writes, verifies postimages, and persists an applied receipt inside the same cooperative workspace lock. A later CLI invocation can replay that receipt idempotently, including after the preparation TTL.
Guarantee boundary
The server does not claim a filesystem-wide transaction or safety against a continuously mutating writer:
- Each replacement exchanges and authenticates one staged/destination pair. Multi-file apply remains sequential, so earlier postimages may be visible before later files commit.
- Reverse rollback restores only an exact operation-owned pair. Lost ownership preserves the observable entries and returns `AMBIGUOUS_APPLY`; it never falls back to pathname replacement.
- Creation is no-clobber. Once a created inode is published, a later failure cannot safely infer unlink ownership, so the destination and hidden stage are preserved and rollback fails closed as ambiguous.
- MCP and CLI apply share a fail-closed filesystem lock keyed by canonical `tsconfig.json` when they use the same state directory. It does not coordinate editors, NFS writers, writers using another configuration, or hostile external processes.
- Deterministic promise barriers prove the tested publication/rollback interleavings. They are a threat-boundary test seam, not a global atomicity or continuous-writer guarantee.
- Receipt persistence runs before that lock is released. If receipt storage fails after source replacement, apply exits non-zero and reports that verified postimages may be present; retry recovers the receipt only when the complete workspace exactly matches the reviewed post-workspace fingerprint.
- A hard process crash can leave a stale lock. Remove it only after inspecting its metadata and proving no apply is running; exact complete postimages can then recover the receipt, while partial or divergent state remains a conflict.
- Source encoding support is UTF-8, with or without BOM. Unsupported encodings are rejected.
Development gates
Add or change an MCP tool
Keep schemas, metadata, annotations, handlers, errors, and serialization in the tool module. Export
one frozen descriptor, add it to the intentional order in `src/tools/catalog.ts`, and declare only
static effect, batch, compatibility, and direct-format facts. Do not add request state, dynamic
discovery, invocation-by-name, or a generic executor to the catalog.
Update the independent `tools/list` inventories only after reviewing the complete wire metadata;
they must not import or derive expectations from the catalog. Then run the focused catalog and MCP
integration tests plus the runtime, package, and managed-skill gates below.
yarn vitest run test/tool-catalog.test.ts test/mcp.integration.test.ts
yarn format:check
yarn lint
yarn typecheck
yarn test
yarn build
yarn test:mcp
yarn test:cli
yarn test:package
yarn test:installed-agents
yarn npm audit --all --recursive
yarn pack --dry-run`test:mcp` exercises the built stdio server. `test:cli` runs a read pipeline and a prepare/apply/replay workflow across separate Node processes. `test:installed-agents` is a host-dependent manual gate: it builds first, detects locally installed supported clients, uses only disposable homes/config roots, verifies deterministic effective discovery without model calls, reports unavailable clients, and removes the disposable state. It is not a portable CI requirement because CI does not install every external client.
Benchmarks
yarn benchmark /absolute/project --sample 20 --output benchmark/results/project.json
yarn benchmark:corpus benchmark/task-corpus.json --output benchmark/results/self-corpus.json
yarn benchmark:batch --iterations 5 --output benchmark/results/self-batch.json
yarn benchmark:formats
yarn benchmark:shapesThe batch benchmark compares two separate client calls with one batch invocation in fresh Node processes, recording model round-trips, actual tool invocations, wall time, maximum RSS, and serialized character counts. Character counts are not model-specific token estimates.
The format benchmark runs real tools against this repository plus deterministic reference/diagnostic fixtures. It checks JSON→TOON→value equality, UTF-8 bytes, `gpt-tokenizer` `o200k_base` estimates, encode/decode latency, the actual MCP envelope, tool metadata, and negative controls. Its checked result is `benchmark/results/self-formats.json`; local tokenizer estimates do not establish provider-side billing or cache savings. See `benchmark/README.md` for methodology and limitations.
The result-shaping benchmark compares the v0.4.0-compatible `full/100/context` profiles with the new public defaults across exact-name, exact-path, prefix, broad-substring, and multi-file-reference tasks. It fails on missing evidence, extra required calls, fewer than the benchmark's required minimum tool surface, or less than 35% aggregate TOON token reduction. Its checked result is `benchmark/results/self-result-shapes.json`.
Scope
- TypeScript and JavaScript projects understood by the TypeScript compiler.
- Structural rename, callable-body replacement, and reviewed creation of one class scaffold.
- Declarative DAG-like pipelines with prior-result references and bounded foreach.
- No arbitrary signature migration, general file creation/deletion, cross-language refactors, or general-purpose scripting language.
Frequently asked questions
What is ast-mcp-server?
ast-mcp-server is Correctness-oriented MCP server and batch CLI for compact structural reads and reviewed TypeScript/JavaScript edits.
How do I install ast-mcp-server?
Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.
Is ast-mcp-server open source?
Yes — it is hosted on GitHub at https://github.com/yailPeralta/ast-mcp-server and has 4 stars.
Related MCP tools
A super light-weight embedded code search engine CLI (AST based) that just works - improves speed and efficiency for coding agent 🌟 Star if you like it!
The Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra
A MCP for Claude Desktop / Claude Code / Windsurf / Cursor to build n8n workflows for you
Browser MCP is a Model Context Provider (MCP) server that allows AI applications to control your browser
A Model Context Protocol (MCP) server and CLI that provides tools for agent use when working on iOS and macOS projects.
The go-to web for your AI coding agent — local-first search, fetch, crawl & research over MCP. No API keys, no cloud, $0/query. Public beta.
Run your own MCP server? See who uses it and what to fix.
Measure it with TrackMCP