ast-mcp-server 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +28 -8
- package/dist/batch/runner.js +10 -2
- package/dist/batch/schema.js +8 -1
- package/dist/cli-output.js +28 -0
- package/dist/cli.js +57 -9
- package/dist/server.js +6 -1
- package/dist/services/agent-setup.js +1 -0
- package/dist/services/operation-plan-file.js +1 -1
- package/dist/services/operations.js +172 -17
- package/dist/services/pagination.js +16 -10
- package/dist/services/scaffold.js +241 -0
- package/dist/services/symbols.js +16 -0
- package/dist/tools/apply_operation.js +1 -1
- package/dist/tools/find_references.js +32 -9
- package/dist/tools/get_diagnostics.js +4 -4
- package/dist/tools/operation-schema.js +1 -1
- package/dist/tools/result.js +45 -1
- package/dist/tools/scaffold_class.js +145 -0
- package/dist/tools/search_symbols.js +79 -18
- package/package.json +9 -2
- package/skills/structural-code-editing/SKILL.md +30 -6
- package/benchmark/README.md +0 -86
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,48 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.5.0] - 2026-08-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Relevance-ranked symbol search with `selectors`, `summary`, and backward-compatible `full` detail profiles.
|
|
12
|
+
- Progressive semantic references with location-only defaults and opt-in bounded source context.
|
|
13
|
+
- `ast_scaffold_class`, a strict prepare-only class scaffold that generates explicit throwing method placeholders and uses the existing preview/apply protocol.
|
|
14
|
+
- Hash-bound absent-file plans with atomic no-clobber creation, conservative rollback, persisted replay, and idempotent receipt recovery.
|
|
15
|
+
- A checked result-shaping workflow benchmark covering ranking, selector chaining, multi-file references, JSON/TOON payloads, call counts, evidence preservation, and complete tool metadata.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Symbol search now defaults to ranked `summary` records with a 20-result page. Use `detail: "full", limit: 100` for the v0.4.0 result profile.
|
|
20
|
+
- Reference search now defaults to `locations` without source lines. Use `detail: "context"` for the v0.4.0 record profile.
|
|
21
|
+
- MCP and batch client identity now derives from the shipped package version instead of a separately maintained literal.
|
|
22
|
+
|
|
23
|
+
### Security
|
|
24
|
+
|
|
25
|
+
- Scaffold inputs are strict and bounded; identifiers, duplicates, raw TypeScript fragments, paths, parent directories, and target absence are validated before plan creation.
|
|
26
|
+
- File creation uses same-directory exclusive staging plus an atomic hard-link commit and never falls back to overwrite-capable rename.
|
|
27
|
+
- Existing rename/body-replacement plan hashes and persisted modification plans remain compatible.
|
|
28
|
+
|
|
29
|
+
## [0.4.0] - 2026-08-03
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Opt-in TOON envelopes for symbol search, semantic references, and diagnostics through `output_format: "toon"`.
|
|
34
|
+
- Plain TOON output for final read-only batch results through `ast-tool run --output-format toon`.
|
|
35
|
+
- Reproducible JSON/TOON benchmark with lossless round trips, UTF-8 bytes, `o200k_base` token estimates, encoding latency, static tool-metadata accounting, and negative controls.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- Collection-heavy tools validate their canonical Zod outputs explicitly before returning JSON or TOON. They no longer advertise one MCP `outputSchema` because their structured result can be either the canonical object or a TOON envelope.
|
|
40
|
+
- The bundled structural-code-editing skill now selects TOON only for measured collection shapes and keeps JSON for source, outline, file-list, and mutation workflows.
|
|
41
|
+
|
|
42
|
+
### Security
|
|
43
|
+
|
|
44
|
+
- Batch steps reject statically declared and dynamically resolved TOON intermediates before the affected invocation.
|
|
45
|
+
- TOON output is capped at 10 MiB after UTF-8 encoding, and mutation preparation remains JSON-only so review coordinates are never compacted.
|
|
46
|
+
- Successful MCP TOON output is decode-checked against the validated value, and MCP error text is capped at 64 KiB of UTF-8.
|
|
47
|
+
- The transitive Hono runtime is resolved to `4.12.34`, closing GHSA-8j4g-w8fx-2239 from the MCP SDK dependency tree.
|
|
48
|
+
|
|
7
49
|
## [0.3.0] - 2026-08-03
|
|
8
50
|
|
|
9
51
|
Initial public release.
|
|
@@ -24,4 +66,6 @@ Initial public release.
|
|
|
24
66
|
- Apply rejects stale workspaces, changed configs/sources, mismatched plan hashes, unsafe plan files, conflicting MCP registrations, and conflicting skill content.
|
|
25
67
|
- Inputs, outputs, operation stores, subprocesses, batch fan-out, plan lifetime, and filesystem access are bounded.
|
|
26
68
|
|
|
69
|
+
[0.5.0]: https://github.com/yailPeralta/ast-mcp-server/releases/tag/v0.5.0
|
|
70
|
+
[0.4.0]: https://github.com/yailPeralta/ast-mcp-server/releases/tag/v0.4.0
|
|
27
71
|
[0.3.0]: https://github.com/yailPeralta/ast-mcp-server/releases/tag/v0.3.0
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ Reads can start with a compact outline and fetch exact source only when needed.
|
|
|
39
39
|
|
|
40
40
|
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.
|
|
41
41
|
|
|
42
|
-
The included batch benchmark records a 50% reduction in model round-trips and a
|
|
42
|
+
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. These are reproducible local `o200k_base` estimates, not universal token, billing, cache, or latency claims.
|
|
43
43
|
|
|
44
44
|
## Requirements
|
|
45
45
|
|
|
@@ -224,11 +224,22 @@ Project-scoped tools accept `project_root`, either the project directory or an e
|
|
|
224
224
|
| `ast_get_diagnostics` | Project- or file-scoped TypeScript diagnostics | No |
|
|
225
225
|
| `ast_rename_symbol` | Prepare a project-wide rename | No |
|
|
226
226
|
| `ast_replace_symbol_body` | Prepare a body-only replacement while preserving the signature | No |
|
|
227
|
+
| `ast_scaffold_class` | Prepare one new class file with explicit placeholder methods | No |
|
|
227
228
|
| `ast_get_operation_preview` | Retrieve the complete retained diff for a prepared plan | No |
|
|
228
229
|
| `ast_apply_operation` | Apply one reviewed, hash-bound plan | Yes |
|
|
229
230
|
|
|
230
231
|
Read results use project-relative paths, deterministic ordering, structured MCP output, and pagination where result sets can grow with the project.
|
|
231
232
|
|
|
233
|
+
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.
|
|
234
|
+
|
|
235
|
+
### Optional TOON results
|
|
236
|
+
|
|
237
|
+
`ast_search_symbols`, `ast_find_references`, 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.
|
|
238
|
+
|
|
239
|
+
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 three 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.
|
|
240
|
+
|
|
241
|
+
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.
|
|
242
|
+
|
|
232
243
|
## Batch CLI
|
|
233
244
|
|
|
234
245
|
`ast-tool` lets Claude Code and other Bash-capable clients collapse a known structural pipeline into one shell call:
|
|
@@ -236,6 +247,7 @@ Read results use project-relative paths, deterministic ordering, structured MCP
|
|
|
236
247
|
```bash
|
|
237
248
|
ast-tool validate pipeline.json
|
|
238
249
|
ast-tool run pipeline.json
|
|
250
|
+
ast-tool run pipeline.json --output-format toon
|
|
239
251
|
cat pipeline.json | ast-tool run -
|
|
240
252
|
```
|
|
241
253
|
|
|
@@ -256,7 +268,7 @@ Example search-to-source pipeline:
|
|
|
256
268
|
"tool": "ast_get_symbol_source",
|
|
257
269
|
"input": {
|
|
258
270
|
"file_path": { "$ref": "#/steps/search/symbols/0/file" },
|
|
259
|
-
"symbol_path": { "$ref": "#/steps/search/symbols/0/
|
|
271
|
+
"symbol_path": { "$ref": "#/steps/search/symbols/0/selector" }
|
|
260
272
|
}
|
|
261
273
|
}
|
|
262
274
|
],
|
|
@@ -299,21 +311,23 @@ A `$ref` is an RFC 6901 JSON Pointer rooted at prior step results. References ca
|
|
|
299
311
|
- One project root per pipeline.
|
|
300
312
|
- No branches, eval, embedded JavaScript, while loops, or arbitrary transformations.
|
|
301
313
|
|
|
302
|
-
Success is one JSON value on stdout. Errors are structured JSON on stderr. Exit code 0 is success, 1 is execution/apply failure, and 2 is usage or schema failure.
|
|
314
|
+
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.
|
|
303
315
|
|
|
304
316
|
## Reviewed mutations
|
|
305
317
|
|
|
306
318
|
### MCP process
|
|
307
319
|
|
|
308
|
-
Rename
|
|
320
|
+
Rename, body replacement, and class scaffold never write directly during preparation:
|
|
309
321
|
|
|
310
|
-
1. Call `ast_rename_symbol` or `
|
|
322
|
+
1. Call `ast_rename_symbol`, `ast_replace_symbol_body`, or `ast_scaffold_class`.
|
|
311
323
|
2. Review the diagnostic delta, affected files, `blocked`, and `plan_hash`.
|
|
312
324
|
3. Fetch complete diffs with `ast_get_operation_preview` when needed.
|
|
313
325
|
4. Call `ast_apply_operation` with both `operation_id` and `plan_hash`.
|
|
314
326
|
|
|
315
327
|
MCP plans live in a bounded in-memory store and do not survive a server restart.
|
|
316
328
|
|
|
329
|
+
`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.
|
|
330
|
+
|
|
317
331
|
### CLI process boundary
|
|
318
332
|
|
|
319
333
|
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.
|
|
@@ -370,13 +384,19 @@ yarn pack --dry-run
|
|
|
370
384
|
yarn benchmark /absolute/project --sample 20 --output benchmark/results/project.json
|
|
371
385
|
yarn benchmark:corpus benchmark/task-corpus.json --output benchmark/results/self-corpus.json
|
|
372
386
|
yarn benchmark:batch --iterations 5 --output benchmark/results/self-batch.json
|
|
387
|
+
yarn benchmark:formats
|
|
388
|
+
yarn benchmark:shapes
|
|
373
389
|
```
|
|
374
390
|
|
|
375
|
-
The 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.
|
|
391
|
+
The 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.
|
|
392
|
+
|
|
393
|
+
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.
|
|
394
|
+
|
|
395
|
+
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 logical calls, fewer than 11 tools, or less than 35% aggregate TOON token reduction. Its checked result is `benchmark/results/self-result-shapes.json`.
|
|
376
396
|
|
|
377
397
|
## Scope
|
|
378
398
|
|
|
379
399
|
- TypeScript and JavaScript projects understood by the TypeScript compiler.
|
|
380
|
-
- Structural rename
|
|
400
|
+
- Structural rename, callable-body replacement, and reviewed creation of one class scaffold.
|
|
381
401
|
- Declarative DAG-like pipelines with prior-result references and bounded foreach.
|
|
382
|
-
- No arbitrary signature migration, file creation/deletion
|
|
402
|
+
- No arbitrary signature migration, general file creation/deletion, cross-language refactors, or general-purpose scripting language.
|
package/dist/batch/runner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { performance } from "node:perf_hooks";
|
|
2
2
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
3
|
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
4
|
-
import { createServer } from "../server.js";
|
|
4
|
+
import { createServer, PACKAGE_VERSION } from "../server.js";
|
|
5
5
|
import { MAX_BATCH_CONTEXT_BYTES, MAX_BATCH_INVOCATIONS, MAX_BATCH_OUTPUT_BYTES, MAX_FOREACH_ITEMS, isItemTemplate, isPrepareBatchTool, isReadBatchTool, isReferenceTemplate, parseBatchDocument as parseDocument, } from "./schema.js";
|
|
6
6
|
export { parseDocument as parseBatchDocument };
|
|
7
7
|
export class BatchExecutionError extends Error {
|
|
@@ -65,6 +65,9 @@ function injectProjectRoot(resolved, projectRoot, step) {
|
|
|
65
65
|
throw new BatchExecutionError(`Step "${step.id}" input must resolve to an object.`, "INVALID_STEP_INPUT", step.id);
|
|
66
66
|
}
|
|
67
67
|
const input = resolved;
|
|
68
|
+
if (input.output_format === "toon") {
|
|
69
|
+
throw new BatchExecutionError(`Step "${step.id}" cannot resolve TOON for an intermediate batch result.`, "TOON_INTERMEDIATE_FORBIDDEN", step.id);
|
|
70
|
+
}
|
|
68
71
|
if (Object.prototype.hasOwnProperty.call(input, "project_root") &&
|
|
69
72
|
input.project_root !== projectRoot) {
|
|
70
73
|
throw new BatchExecutionError(`Step "${step.id}" resolves a conflicting project_root.`, "PROJECT_ROOT_CONFLICT", step.id);
|
|
@@ -73,7 +76,7 @@ function injectProjectRoot(resolved, projectRoot, step) {
|
|
|
73
76
|
}
|
|
74
77
|
async function connectBatchClient() {
|
|
75
78
|
const server = createServer();
|
|
76
|
-
const client = new Client({ name: "ast-tool-batch", version:
|
|
79
|
+
const client = new Client({ name: "ast-tool-batch", version: PACKAGE_VERSION });
|
|
77
80
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
78
81
|
await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
|
|
79
82
|
return { client, server };
|
|
@@ -170,6 +173,11 @@ export async function runBatchDocument(document, options = {}) {
|
|
|
170
173
|
if (items.length > MAX_FOREACH_ITEMS) {
|
|
171
174
|
throw new BatchExecutionError(`Step "${step.id}" expands to ${items.length} items; maximum is ${MAX_FOREACH_ITEMS}.`, "FOREACH_LIMIT", step.id);
|
|
172
175
|
}
|
|
176
|
+
// Resolve once up front so one forbidden item cannot race valid sibling invocations.
|
|
177
|
+
// Resolve again inside mapLimit to avoid retaining up to 200 cloned input payloads.
|
|
178
|
+
for (const item of items) {
|
|
179
|
+
injectProjectRoot(resolveTemplate(step.input, context, item), document.project_root, step);
|
|
180
|
+
}
|
|
173
181
|
reserveInvocations(items.length, step);
|
|
174
182
|
const results = await mapLimit(items, document.limits.concurrency, async (item) => {
|
|
175
183
|
const resolved = resolveTemplate(step.input, context, item);
|
package/dist/batch/schema.js
CHANGED
|
@@ -16,7 +16,11 @@ export const READ_BATCH_TOOLS = [
|
|
|
16
16
|
"ast_find_references",
|
|
17
17
|
"ast_get_diagnostics",
|
|
18
18
|
];
|
|
19
|
-
export const PREPARE_BATCH_TOOLS = [
|
|
19
|
+
export const PREPARE_BATCH_TOOLS = [
|
|
20
|
+
"ast_rename_symbol",
|
|
21
|
+
"ast_replace_symbol_body",
|
|
22
|
+
"ast_scaffold_class",
|
|
23
|
+
];
|
|
20
24
|
export const BATCH_TOOLS = [...READ_BATCH_TOOLS, ...PREPARE_BATCH_TOOLS];
|
|
21
25
|
const BatchToolSchema = z.enum(BATCH_TOOLS);
|
|
22
26
|
const BatchStepSchema = z
|
|
@@ -112,6 +116,9 @@ export function parseBatchDocument(input) {
|
|
|
112
116
|
for (const [index, step] of parsed.steps.entries()) {
|
|
113
117
|
if (ids.has(step.id))
|
|
114
118
|
throw new Error(`Duplicate batch step id "${step.id}".`);
|
|
119
|
+
if (step.input.output_format === "toon") {
|
|
120
|
+
throw new Error(`Step "${step.id}" cannot request TOON for an intermediate batch result. Use CLI --output-format toon for the final output.`);
|
|
121
|
+
}
|
|
115
122
|
if (Object.hasOwn(step.input, "project_root")) {
|
|
116
123
|
if (step.input.project_root !== parsed.project_root) {
|
|
117
124
|
throw new Error(`Step "${step.id}" project_root conflicts with the pipeline project_root.`);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { encode } from "@toon-format/toon";
|
|
3
|
+
import { MAX_BATCH_OUTPUT_BYTES } from "./batch/schema.js";
|
|
4
|
+
export class CliOutputError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(message, code, options) {
|
|
7
|
+
super(message, options);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "CliOutputError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function serializeCliSuccess(value, outputFormat, maxBytes = MAX_BATCH_OUTPUT_BYTES) {
|
|
13
|
+
let serialized;
|
|
14
|
+
try {
|
|
15
|
+
serialized = outputFormat === "toon" ? encode(value) : JSON.stringify(value);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new CliOutputError(`Failed to encode ${outputFormat.toUpperCase()} output.`, "ENCODING_ERROR", { cause: error });
|
|
19
|
+
}
|
|
20
|
+
if (serialized === undefined) {
|
|
21
|
+
throw new CliOutputError(`${outputFormat.toUpperCase()} output did not produce a document.`, "ENCODING_ERROR");
|
|
22
|
+
}
|
|
23
|
+
const bytes = Buffer.byteLength(serialized, "utf8");
|
|
24
|
+
if (bytes > maxBytes) {
|
|
25
|
+
throw new CliOutputError(`${outputFormat === "toon" ? "TOON" : "JSON"} output exceeds the ${maxBytes}-byte limit (${bytes} bytes encoded).`, "OUTPUT_LIMIT");
|
|
26
|
+
}
|
|
27
|
+
return serialized;
|
|
28
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,8 @@ import process from "node:process";
|
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { ZodError } from "zod";
|
|
6
6
|
import { BatchExecutionError, parseBatchDocument, runBatchDocument } from "./batch/runner.js";
|
|
7
|
-
import { MAX_BATCH_INPUT_BYTES } from "./batch/schema.js";
|
|
7
|
+
import { MAX_BATCH_INPUT_BYTES, isPrepareBatchTool } from "./batch/schema.js";
|
|
8
|
+
import { CliOutputError, serializeCliSuccess } from "./cli-output.js";
|
|
8
9
|
import { applyPersistedOperation, persistOperationPlan } from "./services/operation-plan-file.js";
|
|
9
10
|
import { AgentSetupError, detectInstalledAgents, resolveServerEntryPath, runAgentSetup, } from "./services/agent-setup.js";
|
|
10
11
|
import { installBundledSkill, resolveBundledSkillPath, } from "./services/skill-installer.js";
|
|
@@ -28,7 +29,7 @@ class CliError extends Error {
|
|
|
28
29
|
function usage() {
|
|
29
30
|
return [
|
|
30
31
|
"Usage:",
|
|
31
|
-
" ast-tool run <pipeline.json|->",
|
|
32
|
+
" ast-tool run <pipeline.json|-> [--output-format json|toon]",
|
|
32
33
|
" ast-tool validate <pipeline.json|->",
|
|
33
34
|
" ast-tool apply <plan.astplan> --plan-hash <sha256>",
|
|
34
35
|
" ast-tool install-skill [claude|hermes|all] [--scope user|project] [--project-root <path>] [--force]",
|
|
@@ -68,6 +69,32 @@ function parseApplyArgs(args) {
|
|
|
68
69
|
}
|
|
69
70
|
return { planFile: args[0], planHash: args[2] };
|
|
70
71
|
}
|
|
72
|
+
function parseRunArgs(args) {
|
|
73
|
+
let source;
|
|
74
|
+
let outputFormat = "json";
|
|
75
|
+
let outputFormatSeen = false;
|
|
76
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
77
|
+
const argument = args[index];
|
|
78
|
+
if (argument === "--output-format") {
|
|
79
|
+
const value = args[index + 1];
|
|
80
|
+
if (outputFormatSeen || (value !== "json" && value !== "toon")) {
|
|
81
|
+
throw new CliError(usage(), "USAGE", 2, "run");
|
|
82
|
+
}
|
|
83
|
+
outputFormat = value;
|
|
84
|
+
outputFormatSeen = true;
|
|
85
|
+
index += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (source === undefined && !argument.startsWith("--")) {
|
|
89
|
+
source = argument;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
throw new CliError(usage(), "USAGE", 2, "run");
|
|
93
|
+
}
|
|
94
|
+
if (!source)
|
|
95
|
+
throw new CliError(usage(), "USAGE", 2, "run");
|
|
96
|
+
return { source, outputFormat };
|
|
97
|
+
}
|
|
71
98
|
function parseInstallSkillArgs(args) {
|
|
72
99
|
let target = "all";
|
|
73
100
|
let targetSeen = false;
|
|
@@ -168,10 +195,14 @@ async function parseDocumentForCommand(source, command) {
|
|
|
168
195
|
export async function runCli(args) {
|
|
169
196
|
const [command, ...commandArgs] = args;
|
|
170
197
|
if (command === "run" || command === "validate") {
|
|
171
|
-
|
|
198
|
+
const runArgs = command === "run"
|
|
199
|
+
? parseRunArgs(commandArgs)
|
|
200
|
+
: commandArgs.length === 1 && commandArgs[0]
|
|
201
|
+
? { source: commandArgs[0], outputFormat: "json" }
|
|
202
|
+
: undefined;
|
|
203
|
+
if (!runArgs)
|
|
172
204
|
throw new CliError(usage(), "USAGE", 2, command);
|
|
173
|
-
|
|
174
|
-
const document = await parseDocumentForCommand(commandArgs[0], command);
|
|
205
|
+
const document = await parseDocumentForCommand(runArgs.source, command);
|
|
175
206
|
if (command === "validate") {
|
|
176
207
|
return {
|
|
177
208
|
version: 1,
|
|
@@ -179,6 +210,10 @@ export async function runCli(args) {
|
|
|
179
210
|
step_count: document.steps.length,
|
|
180
211
|
};
|
|
181
212
|
}
|
|
213
|
+
if (runArgs.outputFormat === "toon" &&
|
|
214
|
+
document.steps.some((step) => isPrepareBatchTool(step.tool))) {
|
|
215
|
+
throw new CliError("TOON output is available only for read-only batches; prepare results require JSON review coordinates.", "INVALID_BATCH", 2, command);
|
|
216
|
+
}
|
|
182
217
|
try {
|
|
183
218
|
return await runBatchDocument(document, {
|
|
184
219
|
persistPreparedOperation: (operationId) => persistOperationPlan(operationId),
|
|
@@ -262,7 +297,7 @@ export async function runCli(args) {
|
|
|
262
297
|
}
|
|
263
298
|
throw new CliError(usage(), "USAGE", 2, command ?? null);
|
|
264
299
|
}
|
|
265
|
-
function failure(error) {
|
|
300
|
+
function failure(error, command = null) {
|
|
266
301
|
if (error instanceof CliError) {
|
|
267
302
|
return {
|
|
268
303
|
value: {
|
|
@@ -276,6 +311,17 @@ function failure(error) {
|
|
|
276
311
|
exitCode: error.exitCode,
|
|
277
312
|
};
|
|
278
313
|
}
|
|
314
|
+
if (error instanceof CliOutputError) {
|
|
315
|
+
return {
|
|
316
|
+
value: {
|
|
317
|
+
status: "error",
|
|
318
|
+
command,
|
|
319
|
+
code: error.code,
|
|
320
|
+
message: error.message,
|
|
321
|
+
},
|
|
322
|
+
exitCode: 1,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
279
325
|
return {
|
|
280
326
|
value: {
|
|
281
327
|
status: "error",
|
|
@@ -287,12 +333,14 @@ function failure(error) {
|
|
|
287
333
|
};
|
|
288
334
|
}
|
|
289
335
|
async function main() {
|
|
336
|
+
const args = process.argv.slice(2);
|
|
290
337
|
try {
|
|
291
|
-
const result = await runCli(
|
|
292
|
-
|
|
338
|
+
const result = await runCli(args);
|
|
339
|
+
const outputFormat = args[0] === "run" ? parseRunArgs(args.slice(1)).outputFormat : "json";
|
|
340
|
+
process.stdout.write(`${serializeCliSuccess(result, outputFormat)}\n`);
|
|
293
341
|
}
|
|
294
342
|
catch (error) {
|
|
295
|
-
const failed = failure(error);
|
|
343
|
+
const failed = failure(error, args[0] ?? null);
|
|
296
344
|
process.stderr.write(`${JSON.stringify(failed.value)}\n`);
|
|
297
345
|
process.exitCode = failed.exitCode;
|
|
298
346
|
}
|
package/dist/server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
3
|
import { registerApplyOperation } from "./tools/apply_operation.js";
|
|
3
4
|
import { registerFindReferences } from "./tools/find_references.js";
|
|
@@ -8,11 +9,14 @@ import { registerGetSymbolSource } from "./tools/get_symbol_source.js";
|
|
|
8
9
|
import { registerListFiles } from "./tools/list_files.js";
|
|
9
10
|
import { registerRenameSymbol } from "./tools/rename_symbol.js";
|
|
10
11
|
import { registerReplaceSymbolBody } from "./tools/replace_symbol_body.js";
|
|
12
|
+
import { registerScaffoldClass } from "./tools/scaffold_class.js";
|
|
11
13
|
import { registerSearchSymbols } from "./tools/search_symbols.js";
|
|
14
|
+
const packageMetadata = createRequire(import.meta.url)("../package.json");
|
|
15
|
+
export const PACKAGE_VERSION = packageMetadata.version;
|
|
12
16
|
export function createServer() {
|
|
13
17
|
const server = new McpServer({
|
|
14
18
|
name: "ast-mcp-server",
|
|
15
|
-
version:
|
|
19
|
+
version: PACKAGE_VERSION,
|
|
16
20
|
});
|
|
17
21
|
registerListFiles(server);
|
|
18
22
|
registerGetOutline(server);
|
|
@@ -22,6 +26,7 @@ export function createServer() {
|
|
|
22
26
|
registerGetDiagnostics(server);
|
|
23
27
|
registerRenameSymbol(server);
|
|
24
28
|
registerReplaceSymbolBody(server);
|
|
29
|
+
registerScaffoldClass(server);
|
|
25
30
|
registerGetOperationPreview(server);
|
|
26
31
|
registerApplyOperation(server);
|
|
27
32
|
return server;
|
|
@@ -42,7 +42,7 @@ const PersistedOperationSchema = z
|
|
|
42
42
|
.object({
|
|
43
43
|
operation_id: z.string().uuid(),
|
|
44
44
|
plan_hash: HashSchema,
|
|
45
|
-
kind: z.enum(["rename_symbol", "replace_symbol_body"]),
|
|
45
|
+
kind: z.enum(["rename_symbol", "replace_symbol_body", "scaffold_class"]),
|
|
46
46
|
status: z.enum(["prepared", "applied"]),
|
|
47
47
|
project_root: z.string().min(1),
|
|
48
48
|
created_at: IsoDateSchema,
|