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 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 95.21% reduction in serialized context for its search-to-source scenario. These are reproducible scenario measurements, not universal token or latency claims.
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/symbol_path" }
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 and body replacement never write directly:
320
+ Rename, body replacement, and class scaffold never write directly during preparation:
309
321
 
310
- 1. Call `ast_rename_symbol` or `ast_replace_symbol_body`.
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. See `benchmark/README.md` for methodology and limitations.
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 and callable-body replacement.
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 plans, cross-language refactors, or general-purpose scripting language.
402
+ - No arbitrary signature migration, general file creation/deletion, cross-language refactors, or general-purpose scripting language.
@@ -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: "0.3.0" });
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);
@@ -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 = ["ast_rename_symbol", "ast_replace_symbol_body"];
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
- if (commandArgs.length !== 1 || !commandArgs[0]) {
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(process.argv.slice(2));
292
- process.stdout.write(`${JSON.stringify(result)}\n`);
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: "0.3.0",
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;
@@ -17,6 +17,7 @@ const EXPECTED_HERMES_TOOLS = [
17
17
  "ast_get_diagnostics",
18
18
  "ast_rename_symbol",
19
19
  "ast_replace_symbol_body",
20
+ "ast_scaffold_class",
20
21
  "ast_get_operation_preview",
21
22
  "ast_apply_operation",
22
23
  ];
@@ -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,