ast-mcp-server 0.11.1 → 0.12.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +35 -2
  2. package/README.md +32 -2
  3. package/SECURITY.md +1 -1
  4. package/dist/batch/schema.js +7 -16
  5. package/dist/compiler-worker-entry.js +51 -0
  6. package/dist/index.js +20 -120
  7. package/dist/server.js +2 -32
  8. package/dist/services/agent-targets.js +2 -14
  9. package/dist/services/compiler-worker-host.js +422 -0
  10. package/dist/services/compiler-worker-protocol.js +95 -0
  11. package/dist/services/diagnostics.js +55 -0
  12. package/dist/services/operations.js +3 -1
  13. package/dist/services/project.js +7 -0
  14. package/dist/services/public-errors.js +6 -3
  15. package/dist/services/runtime-logger.js +5 -0
  16. package/dist/services/runtime-policy.js +61 -0
  17. package/dist/tools/apply_operation.js +5 -2
  18. package/dist/tools/catalog.js +80 -0
  19. package/dist/tools/explore.js +5 -2
  20. package/dist/tools/find_references.js +5 -2
  21. package/dist/tools/find_test_candidates.js +5 -2
  22. package/dist/tools/get_diagnostics.js +36 -4
  23. package/dist/tools/get_file.js +5 -2
  24. package/dist/tools/get_impact.js +5 -2
  25. package/dist/tools/get_operation_preview.js +5 -2
  26. package/dist/tools/get_outline.js +5 -2
  27. package/dist/tools/get_project_status.js +5 -2
  28. package/dist/tools/get_symbol_source.js +5 -2
  29. package/dist/tools/list_files.js +5 -2
  30. package/dist/tools/rename_symbol.js +5 -2
  31. package/dist/tools/replace_symbol_body.js +5 -2
  32. package/dist/tools/scaffold_class.js +5 -2
  33. package/dist/tools/search_symbols.js +5 -2
  34. package/dist/worker-runtime.js +126 -0
  35. package/docs/support.md +22 -6
  36. package/package.json +3 -3
  37. package/skills/structural-code-editing/SKILL.md +14 -2
  38. package/skills/structural-code-editing/releases.json +17 -2
package/CHANGELOG.md CHANGED
@@ -6,7 +6,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- ## [0.11.1] — local recovery candidate, pending release (2026-08-23)
9
+ ## [0.12.0] — local release candidate, pending release (2026-08-27)
10
+
11
+ ### Added
12
+
13
+ - Diagnostic pagination can now include opt-in bounded aggregates computed from the complete normalized snapshot, with code and file dimensions capped at 20 entries and explicit coverage metadata.
14
+ - Added an immutable tool capability catalog with MCP, batch, and agent projections derived from one source of truth, plus independent inventory, integration, canary, package, and documentation oracles.
15
+
16
+ ### Fixed
17
+
18
+ - Canary validation continues to enforce the established public runtime gate payload while capability metadata is checked independently, preventing an internal catalog change from widening the public canary schema.
19
+
20
+ ### Release status
21
+
22
+ - Version `0.12.0` remains pending its exact-tree two-runtime matrix, CI, and separately authorized delivery sequence. It has not been published, assigned to an npm dist-tag, tagged in Git, or released on GitHub.
23
+
24
+ ## [0.11.2] — published to `latest` and `next`, registry verified (2026-08-25)
25
+
26
+ ### Added
27
+
28
+ - Added an explicit per-connection supervised compiler-worker mode that can recycle an idle compiler child while the local stdio parent remains connected; `in_process` remains the default and rollback, and TTL `0` disables recycling.
29
+ - Added scoped Linux evidence on exact Node.js 22.13.0 and Node.js 24 for PSS reclamation, stable compiler fingerprints, SQLite reuse, bounded redaction, and parent-death cleanup without orphans.
30
+
31
+ ### Fixed
32
+
33
+ - Compiler-worker startup now waits for `ready` before replaying initialization, preventing cross-channel settlement ordering from rejecting the first forwarded request as stale.
34
+ - CLI project-discovery fixtures now create the external project directory before concurrent configuration writes, eliminating a nondeterministic `ENOENT` setup race.
35
+ - Public-registry verification now copies the isolated runner's complete local runtime dependency closure, including the shared managed-skill validator, before launching it outside the source tree.
36
+ - A deterministic copied-runner regression executes the isolated entry point and rejects any local import omitted by its production bootstrap.
37
+
38
+ ### Release status
39
+
40
+ - npm published these immutable bytes under `next` with provenance and the expected Git commit. Public-registry consumer and release-preflight verification passed, `latest` and `next` both resolve to `0.11.2`, and the annotated `v0.11.2` tag plus GitHub Release resolve to the same release commit. This release supersedes immutable versions `0.11.0` and `0.11.1`, whose separate public-registry verification defects prevented promotion.
41
+
42
+ ## [0.11.1] — published to `next`, verification failed, not promoted (2026-08-23)
10
43
 
11
44
  ### Fixed
12
45
 
@@ -15,7 +48,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
15
48
 
16
49
  ### Release status
17
50
 
18
- - This local recovery candidate succeeds the immutable npm `0.11.0` publication, whose public-registry verification failed because two consumer validators still required skill bundle `4.4.0` while the published package correctly contained `4.5.0`. Version `0.11.0` remains under `next` only and must not be promoted, tagged, or released. Version `0.11.1` remains pending its exact-tree matrix, CI, and separately authorized delivery sequence; it has not been published or assigned to any dist-tag.
51
+ - npm published these immutable bytes under `next` with provenance and the expected Git commit. Public-registry verification then failed because the isolated registry runner omitted its newly imported managed-skill validator module; `0.11.1` was never promoted to `latest`, tagged in Git, or released on GitHub.
19
52
 
20
53
  ## [0.11.0] — published to `next`, verification failed, not promoted (2026-08-23)
21
54
 
package/README.md CHANGED
@@ -84,11 +84,23 @@ The included batch benchmark records a 50% reduction in model round-trips and a
84
84
 
85
85
  ## Supported environment and trust boundary
86
86
 
87
- The local `0.11.1` recovery candidate requires Node.js `>=22.13.0`; its evidence matrix targets exact Node.js 22.13.0 and the current Node.js 24 line. Published v0.10.0 retains its immutable historical Node.js 22.13.0/24 evidence. Managed setup-file publication additionally requires 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`, and `O_DIRECTORY`/`O_NOFOLLOW`. Other Linux architectures or systems without those filesystem primitives, macOS, and Windows remain unverified.
87
+ The local `0.12.0` release candidate requires Node.js `>=22.13.0`; its evidence matrix targets exact Node.js 22.13.0 and the current Node.js 24 line. Published v0.11.2 retains its immutable historical Node.js 22.13.0/24 evidence. Managed setup-file publication additionally requires 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`, and `O_DIRECTORY`/`O_NOFOLLOW`. Other Linux architectures or systems without those filesystem primitives, macOS, and Windows remain unverified.
88
88
 
89
89
  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.
90
90
 
91
- In published v0.10.0 and the local `0.11.1` recovery candidate, 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.
91
+ ### Optional supervised compiler worker
92
+
93
+ 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:
94
+
95
+ ```bash
96
+ AST_COMPILER_WORKER_MODE=supervised ast-mcp-server
97
+ ```
98
+
99
+ The 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.
100
+
101
+ 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](docs/adr/0014-supervised-compiler-worker.md).
102
+
103
+ In published v0.11.2 and the local `0.12.0` release candidate, 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.
92
104
 
93
105
  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:
94
106
 
@@ -333,6 +345,12 @@ Project-scoped tools accept `project_root`, either the project directory or an e
333
345
 
334
346
  Read results use project-relative paths, deterministic ordering, structured MCP output, and pagination where result sets can grow with the project.
335
347
 
348
+ ### Diagnostic aggregates
349
+
350
+ 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.
351
+
352
+ 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.
353
+
336
354
  `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.
337
355
 
338
356
  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](docs/adr/0013-ast-explore-presentation.md).
@@ -474,7 +492,19 @@ The server does **not** claim a filesystem-wide transaction:
474
492
 
475
493
  ## Development gates
476
494
 
495
+ ### Add or change an MCP tool
496
+
497
+ Keep schemas, metadata, annotations, handlers, errors, and serialization in the tool module. Export
498
+ one frozen descriptor, add it to the intentional order in `src/tools/catalog.ts`, and declare only
499
+ static effect, batch, compatibility, and direct-format facts. Do not add request state, dynamic
500
+ discovery, invocation-by-name, or a generic executor to the catalog.
501
+
502
+ Update the independent `tools/list` inventories only after reviewing the complete wire metadata;
503
+ they must not import or derive expectations from the catalog. Then run the focused catalog and MCP
504
+ integration tests plus the runtime, package, and managed-skill gates below.
505
+
477
506
  ```bash
507
+ yarn vitest run test/tool-catalog.test.ts test/mcp.integration.test.ts
478
508
  yarn format:check
479
509
  yarn lint
480
510
  yarn typecheck
package/SECURITY.md CHANGED
@@ -11,7 +11,7 @@ Security fixes are provided for the exact version currently identified by the np
11
11
  | Earlier npm versions | No |
12
12
  | Unpublished release candidates | No |
13
13
 
14
- This project supports trusted single-user local stdio use; the supported operating-system and architecture boundary remains Linux x64 only. The local `0.11.1` recovery candidate requires Node.js `>=22.13.0`, with evidence targeted at exact Node.js 22.13.0 and the governed Node.js 24 major. Published v0.10.0 retains its immutable Node.js 22.13.0/24 release matrix. Managed setup mutation requires GNU coreutils 9.7 `mv --update=none-fail --exchange --no-copy --no-target-directory`, GNU coreutils `ln -L -T`, and procfs descriptor-relative paths at `/proc/self/fd` with `O_DIRECTORY`/`O_NOFOLLOW`. The candidate's default SQLite cache also relies on Linux ownership, mode, no-follow, link-count and inode-identity checks; it does not defend against a malicious same-UID process. Other Linux architectures and systems without those primitives are unverified, as are macOS and Windows. Remote, untrusted, and multi-tenant use is outside the supported security boundary.
14
+ This project supports trusted single-user local stdio use; the supported operating-system and architecture boundary remains Linux x64 only. The local `0.12.0` release candidate requires Node.js `>=22.13.0`, with evidence targeted at exact Node.js 22.13.0 and the governed Node.js 24 major. Published v0.11.2 retains its immutable Node.js 22.13.0/24 release matrix. Managed setup mutation requires GNU coreutils 9.7 `mv --update=none-fail --exchange --no-copy --no-target-directory`, GNU coreutils `ln -L -T`, and procfs descriptor-relative paths at `/proc/self/fd` with `O_DIRECTORY`/`O_NOFOLLOW`. The candidate's default SQLite cache also relies on Linux ownership, mode, no-follow, link-count and inode-identity checks; it does not defend against a malicious same-UID process. Other Linux architectures and systems without those primitives are unverified, as are macOS and Windows. Remote, untrusted, and multi-tenant use is outside the supported security boundary.
15
15
 
16
16
  ## Reporting a vulnerability
17
17
 
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { toolCatalog } from "../tools/catalog.js";
2
3
  export const BATCH_SCHEMA_VERSION = 1;
3
4
  export const MAX_BATCH_STEPS = 50;
4
5
  export const MAX_BATCH_INVOCATIONS = 500;
@@ -8,22 +9,12 @@ export const MAX_BATCH_CONCURRENCY = 16;
8
9
  export const MAX_BATCH_INPUT_BYTES = 1024 * 1024;
9
10
  export const MAX_BATCH_OUTPUT_BYTES = 10 * 1024 * 1024;
10
11
  export const MAX_BATCH_CONTEXT_BYTES = 50 * 1024 * 1024;
11
- export const READ_BATCH_TOOLS = [
12
- "ast_list_files",
13
- "ast_get_outline",
14
- "ast_get_symbol_source",
15
- "ast_search_symbols",
16
- "ast_find_references",
17
- "ast_find_test_candidates",
18
- "ast_explore",
19
- "ast_get_diagnostics",
20
- ];
21
- export const PREPARE_BATCH_TOOLS = [
22
- "ast_rename_symbol",
23
- "ast_replace_symbol_body",
24
- "ast_scaffold_class",
25
- ];
26
- export const BATCH_TOOLS = [...READ_BATCH_TOOLS, ...PREPARE_BATCH_TOOLS];
12
+ export const READ_BATCH_TOOLS = toolCatalog.batch.read;
13
+ export const PREPARE_BATCH_TOOLS = toolCatalog.batch.prepare;
14
+ export const BATCH_TOOLS = Object.freeze([
15
+ ...READ_BATCH_TOOLS,
16
+ ...PREPARE_BATCH_TOOLS,
17
+ ]);
27
18
  const BatchToolSchema = z.enum(BATCH_TOOLS);
28
19
  const BatchStepSchema = z
29
20
  .object({
@@ -0,0 +1,51 @@
1
+ import fs from "node:fs";
2
+ import { createInterface } from "node:readline";
3
+ import { PassThrough } from "node:stream";
4
+ import { fileURLToPath } from "node:url";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { decodeCompilerWorkerEnvelope } from "./services/compiler-worker-protocol.js";
7
+ async function runDefaultCompilerRuntime(input = process.stdin) {
8
+ return (await import("./worker-runtime.js")).runStdioServer({
9
+ transport: new StdioServerTransport(input, process.stdout),
10
+ });
11
+ }
12
+ export function runCompilerWorkerChild(runRuntime = runDefaultCompilerRuntime) {
13
+ return runRuntime();
14
+ }
15
+ // prettier-ignore
16
+ export function installPrivateControl(runtime, input) {
17
+ let sequence = 0, generation = 0, writeSequence = 0;
18
+ const send = (generation, type, payload) => process.send?.({ v: 1, generation, sequence: ++sequence, type, payload });
19
+ const arm = (handle) => { const force = () => { if (handle.snapshot?.().completion_critical_operations > 0)
20
+ setTimeout(force, 50).unref();
21
+ else
22
+ process.exit(1); }; setTimeout(force, (handle.shutdownGraceMs ?? 10_000) + 2_000).unref(); };
23
+ const shutdown = (handle, trigger) => { if (trigger === "parent_exit")
24
+ arm(handle); void handle.shutdown(trigger).finally(() => process.disconnect()); };
25
+ process.on("message", (message) => { const decoded = decodeCompilerWorkerEnvelope(JSON.stringify(message)); if (!decoded.ok)
26
+ return; if (decoded.value.type === "handshake") {
27
+ generation = decoded.value.generation;
28
+ void runtime.then(() => send(generation, "ready", {}));
29
+ } if (decoded.value.type === "snapshot")
30
+ void runtime.then((handle) => send(generation, "snapshot", (handle.snapshot?.() ?? {}))); if (decoded.value.type === "shutdown") {
31
+ const trigger = decoded.value.payload.trigger === "parent_exit" ? "parent_exit" : "requested";
32
+ void runtime.then((handle) => shutdown(handle, trigger));
33
+ } });
34
+ if (input) {
35
+ const lines = createInterface({ input: process.stdin });
36
+ lines.on("line", (line) => input.write(`${line}\n`, () => send(generation, "settled", { write_sequence: ++writeSequence })));
37
+ lines.once("close", () => input.end());
38
+ }
39
+ process.once("disconnect", () => void runtime.then((handle) => { arm(handle); void handle.shutdown("parent_exit").then(() => process.exit(0), () => process.exit(1)); }));
40
+ }
41
+ const entry = process.argv[1];
42
+ if (entry && fs.realpathSync(entry) === fs.realpathSync(fileURLToPath(import.meta.url))) {
43
+ const input = process.send ? new PassThrough() : undefined;
44
+ const runtime = input ? runDefaultCompilerRuntime(input) : runCompilerWorkerChild();
45
+ if (process.send)
46
+ installPrivateControl(runtime, input);
47
+ runtime.catch(() => {
48
+ process.stderr.write("Fatal compiler worker startup error.\n");
49
+ process.exit(1);
50
+ });
51
+ }
package/dist/index.js CHANGED
@@ -1,131 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
- import path from "node:path";
4
3
  import { fileURLToPath } from "node:url";
5
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { createServer } from "./server.js";
7
- import { beginProjectShutdown, closeDrainedProjectSessions, getProjectRuntimeShutdownSnapshot, prepareProjectRuntimeForStartup, waitForProjectCompletionCriticalOperationsToDrain, waitForProjectOperationsToDrain, } from "./services/project.js";
8
- import { RuntimeActivityTracker } from "./services/runtime-activity.js";
9
4
  import { readRuntimePolicy } from "./services/runtime-policy.js";
10
- import { emitIncompleteShutdownEvent, ShutdownCoordinator, } from "./services/shutdown.js";
11
- function emitShutdownFailureEvent() {
12
- process.stderr.write(`${JSON.stringify({ event: "shutdown_failed", version: 1 })}\n`);
5
+ export async function loadRuntimeModule(policy, loaders) {
6
+ return {
7
+ mode: policy.compilerWorkerMode,
8
+ module: await (policy.compilerWorkerMode === "supervised"
9
+ ? loaders.supervised()
10
+ : loaders.inProcess()),
11
+ };
13
12
  }
14
13
  export async function runStdioServer(options = {}) {
15
- if (options.server && !options.runtimeActivity) {
16
- throw new Error("An injected MCP server requires its runtime activity tracker.");
17
- }
18
- prepareProjectRuntimeForStartup();
19
- const runtimeActivity = options.runtimeActivity ?? new RuntimeActivityTracker();
20
- runtimeActivity.prepareForStartup();
21
- const server = options.server ?? createServer({ runtimeActivity });
22
- const transport = options.transport ?? new StdioServerTransport();
23
- const send = transport.send.bind(transport);
24
- transport.send = (message, sendOptions) => runtimeActivity.trackSend(() => send(message, sendOptions));
25
- const runtimePolicy = options.runtimePolicy ?? readRuntimePolicy();
26
- const installProcessHandlers = options.installProcessHandlers ?? true;
27
- const exit = options.exit ?? ((code) => process.exit(code));
28
- const protocol = server.server;
29
- const previousTransportClose = protocol.onclose;
30
- let listenersInstalled = false;
31
- let terminalHandlerInstalled = false;
32
- const onSigint = () => requestFromRuntime("sigint");
33
- const onSigterm = () => requestFromRuntime("sigterm");
34
- const onStdinEnd = () => requestFromRuntime("stdin_eof");
35
- const onStdinClose = () => requestFromRuntime("stdin_eof");
36
- const onTransportClose = () => {
37
- previousTransportClose?.();
38
- requestFromRuntime("transport_closed");
39
- };
40
- const removeLifecycleListeners = () => {
41
- if (listenersInstalled) {
42
- process.off("SIGINT", onSigint);
43
- process.off("SIGTERM", onSigterm);
44
- process.stdin.off("end", onStdinEnd);
45
- process.stdin.off("close", onStdinClose);
46
- listenersInstalled = false;
47
- }
48
- if (protocol.onclose === onTransportClose)
49
- protocol.onclose = previousTransportClose;
50
- };
51
- const coordinator = new ShutdownCoordinator({
52
- drainTimeoutMs: runtimePolicy.shutdownDrainTimeoutMs,
53
- stopAdmissionAndCancel: () => {
54
- runtimeActivity.beginShutdown();
55
- return beginProjectShutdown();
56
- },
57
- getDrainSnapshot: getProjectRuntimeShutdownSnapshot,
58
- waitForDrain: async () => {
59
- await Promise.all([waitForProjectOperationsToDrain(), runtimeActivity.waitForIdle()]);
60
- },
61
- waitForCompletionCriticalOperationsToDrain: waitForProjectCompletionCriticalOperationsToDrain,
62
- closeMcp: async () => {
63
- await server.close();
64
- },
65
- closeProjects: closeDrainedProjectSessions,
66
- emitIncompleteShutdown: emitIncompleteShutdownEvent,
67
- onCompletionCriticalWait: options.onCompletionCriticalWait,
68
- });
69
- let lifecycleCompletion;
70
- function requestShutdown(trigger) {
71
- lifecycleCompletion ??= coordinator.shutdown(trigger).finally(removeLifecycleListeners);
72
- return lifecycleCompletion;
73
- }
74
- function requestFromRuntime(trigger) {
75
- try {
76
- options.onRuntimeTrigger?.(trigger);
77
- }
78
- catch {
79
- // Runtime observation is non-authoritative and must never interrupt shutdown.
80
- }
81
- const completion = requestShutdown(trigger);
82
- if (terminalHandlerInstalled)
83
- return;
84
- terminalHandlerInstalled = true;
85
- void completion
86
- .then((result) => {
87
- if (result.state === "forced_noncritical")
88
- exit(1);
89
- })
90
- .catch(() => {
91
- emitShutdownFailureEvent();
92
- exit(1);
93
- });
94
- }
95
- protocol.onclose = onTransportClose;
96
- if (installProcessHandlers) {
97
- process.on("SIGINT", onSigint);
98
- process.on("SIGTERM", onSigterm);
99
- process.stdin.once("end", onStdinEnd);
100
- process.stdin.once("close", onStdinClose);
101
- listenersInstalled = true;
102
- }
103
- try {
104
- await server.connect(transport);
105
- }
106
- catch (error) {
107
- await requestShutdown("requested").catch(() => undefined);
108
- throw error;
109
- }
110
- if (options.logStartup ?? true)
111
- process.stderr.write("ast-mcp-server running over stdio.\n");
112
- return Object.freeze({
113
- shutdown: (trigger = "requested") => requestShutdown(trigger),
114
- });
14
+ return (await import("./worker-runtime.js")).runStdioServer(options);
115
15
  }
116
- function isMainModule() {
117
- const entry = process.argv[1];
118
- if (!entry)
119
- return false;
120
- try {
121
- return fs.realpathSync(entry) === fs.realpathSync(fileURLToPath(import.meta.url));
122
- }
123
- catch {
124
- return path.resolve(entry) === path.resolve(fileURLToPath(import.meta.url));
125
- }
16
+ async function runConfiguredStdioServer() {
17
+ const policy = readRuntimePolicy();
18
+ const selected = await loadRuntimeModule(policy, {
19
+ inProcess: async () => ({ run: () => runStdioServer({ runtimePolicy: policy }) }),
20
+ supervised: async () => ({
21
+ run: () => import("./services/compiler-worker-host.js").then((host) => host.runSupervisedStdioServer()),
22
+ }),
23
+ });
24
+ await selected.module.run();
126
25
  }
127
- if (isMainModule()) {
128
- runStdioServer().catch(() => {
26
+ const entry = process.argv[1];
27
+ if (entry && fs.realpathSync(entry) === fs.realpathSync(fileURLToPath(import.meta.url))) {
28
+ runConfiguredStdioServer().catch(() => {
129
29
  process.stderr.write("Fatal ast-mcp-server startup error.\n");
130
30
  process.exit(1);
131
31
  });
package/dist/server.js CHANGED
@@ -1,22 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { McpServer, } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { PublicOperationalError } from "./services/public-errors.js";
4
- import { registerApplyOperation } from "./tools/apply_operation.js";
5
- import { registerFindReferences } from "./tools/find_references.js";
6
- import { registerFindTestCandidates } from "./tools/find_test_candidates.js";
7
- import { registerGetDiagnostics } from "./tools/get_diagnostics.js";
8
- import { registerGetFile } from "./tools/get_file.js";
9
- import { registerGetImpact } from "./tools/get_impact.js";
10
- import { registerGetOperationPreview } from "./tools/get_operation_preview.js";
11
- import { registerGetOutline } from "./tools/get_outline.js";
12
- import { registerGetProjectStatus } from "./tools/get_project_status.js";
13
- import { registerGetSymbolSource } from "./tools/get_symbol_source.js";
14
- import { registerListFiles } from "./tools/list_files.js";
15
- import { registerRenameSymbol } from "./tools/rename_symbol.js";
16
- import { registerReplaceSymbolBody } from "./tools/replace_symbol_body.js";
17
- import { registerScaffoldClass } from "./tools/scaffold_class.js";
18
- import { registerSearchSymbols } from "./tools/search_symbols.js";
19
- import { registerExplore } from "./tools/explore.js";
4
+ import { toolCatalog } from "./tools/catalog.js";
20
5
  import { createToolErrorContext, errorResult } from "./tools/result.js";
21
6
  const packageMetadata = createRequire(import.meta.url)("../package.json");
22
7
  export const PACKAGE_VERSION = packageMetadata.version;
@@ -40,21 +25,6 @@ export function createServer(options = {}) {
40
25
  });
41
26
  if (options.runtimeActivity)
42
27
  installRuntimeActivityTracking(server, options.runtimeActivity);
43
- registerListFiles(server);
44
- registerGetProjectStatus(server);
45
- registerExplore(server);
46
- registerGetOutline(server);
47
- registerGetSymbolSource(server);
48
- registerSearchSymbols(server);
49
- registerFindReferences(server);
50
- registerGetImpact(server);
51
- registerFindTestCandidates(server);
52
- registerGetDiagnostics(server);
53
- registerGetFile(server);
54
- registerRenameSymbol(server);
55
- registerReplaceSymbolBody(server);
56
- registerScaffoldClass(server);
57
- registerGetOperationPreview(server);
58
- registerApplyOperation(server);
28
+ toolCatalog.registerAll(server);
59
29
  return server;
60
30
  }
@@ -1,18 +1,6 @@
1
+ import { toolCatalog } from "../tools/catalog.js";
1
2
  export const MCP_SERVER_NAME = "ast";
2
- const EXPECTED_TOOLS = [
3
- "ast_list_files",
4
- "ast_get_outline",
5
- "ast_get_symbol_source",
6
- "ast_search_symbols",
7
- "ast_find_references",
8
- "ast_find_test_candidates",
9
- "ast_get_diagnostics",
10
- "ast_rename_symbol",
11
- "ast_replace_symbol_body",
12
- "ast_scaffold_class",
13
- "ast_get_operation_preview",
14
- "ast_apply_operation",
15
- ];
3
+ const EXPECTED_TOOLS = toolCatalog.compatibility.required;
16
4
  function semver(value) {
17
5
  const match = value.match(/(?:^|\s|v)(\d+)\.(\d+)\.(\d+)(?=\s|$|[-+()]|\.(?:\s|$))/);
18
6
  return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined;