ast-mcp-server 0.11.2 → 0.13.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 +23 -2
- package/README.md +60 -10
- package/SECURITY.md +1 -1
- package/cordis.patch.yml +31 -0
- package/dist/batch/schema.js +7 -16
- package/dist/server.js +7 -32
- package/dist/services/agent-setup.js +85 -19
- package/dist/services/agent-targets.js +170 -80
- package/dist/services/diagnostics.js +55 -0
- package/dist/services/opencode-config.js +84 -7
- package/dist/services/runtime-policy.js +15 -0
- package/dist/tools/apply_operation.js +5 -2
- package/dist/tools/catalog.js +83 -0
- package/dist/tools/explore.js +5 -2
- package/dist/tools/find_references.js +5 -2
- package/dist/tools/find_test_candidates.js +5 -2
- package/dist/tools/get_diagnostics.js +36 -4
- package/dist/tools/get_file.js +5 -2
- package/dist/tools/get_impact.js +5 -2
- package/dist/tools/get_operation_preview.js +5 -2
- package/dist/tools/get_outline.js +5 -2
- package/dist/tools/get_project_status.js +5 -2
- package/dist/tools/get_symbol_source.js +5 -2
- package/dist/tools/list_files.js +5 -2
- package/dist/tools/rename_symbol.js +5 -2
- package/dist/tools/replace_symbol_body.js +5 -2
- package/dist/tools/scaffold_class.js +5 -2
- package/dist/tools/search_symbols.js +5 -2
- package/dist/worker-runtime.js +2 -2
- package/docs/support.md +7 -7
- package/package.json +16 -3
- package/skills/structural-code-editing/SKILL.md +14 -2
- package/skills/structural-code-editing/releases.json +22 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
## [0.
|
|
9
|
+
## [0.13.0] — local release candidate, pending release (2026-08-27)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- DeepSeek Harness Developer Preview adapter (roadmap initiative 4, first slice): `cordis.patch.yml` mounts the packaged `ast-mcp-server` stdio command through `@deepseek-ai/dsh-mcp-client`, declared via the exact `"dsh": {"bundle": {"patch": "./cordis.patch.yml"}}` contract with the pinned identity recorded separately in `deepseekHarness` (revision `cd5ef814…`, `dsh-v0.1.2-alpha.1`). The first surface is reads + prepare + preview with a fail-closed apply guard (`ast_apply_operation` registers only under an explicit `AST_MCP_APPLY_GUARD=allow`; unset, `deny`, or invalid denies it). `yarn test:dsh-adapter` is a mandatory, never-skipping gate: tarball fixture and hashes, guard matrix, and — against the clean source-built pinned Harness — native-mode profile composition plus Harness-mediated read, dry-run rename, preview, apply absence, and rejected direct apply invocation.
|
|
14
|
+
|
|
15
|
+
## [0.12.0] — published to `latest` and `next`, registry verified (2026-08-27)
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- 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.
|
|
20
|
+
- 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.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- 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.
|
|
25
|
+
|
|
26
|
+
### Release status
|
|
27
|
+
|
|
28
|
+
- 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.12.0`, and the annotated `v0.12.0` tag plus GitHub Release resolve to the same release commit.
|
|
29
|
+
|
|
30
|
+
## [0.11.2] — published to `latest` and `next`, registry verified (2026-08-25)
|
|
10
31
|
|
|
11
32
|
### Added
|
|
12
33
|
|
|
@@ -22,7 +43,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
22
43
|
|
|
23
44
|
### Release status
|
|
24
45
|
|
|
25
|
-
-
|
|
46
|
+
- 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.
|
|
26
47
|
|
|
27
48
|
## [0.11.1] — published to `next`, verification failed, not promoted (2026-08-23)
|
|
28
49
|
|
package/README.md
CHANGED
|
@@ -55,12 +55,12 @@ Reads can start with a bounded file slice, a compact outline, or exact source on
|
|
|
55
55
|
|
|
56
56
|
The server exposes evidence labels instead of collapsing every result into an unqualified confidence score:
|
|
57
57
|
|
|
58
|
-
| Label | Meaning
|
|
59
|
-
| ----------------------------------------------------------------------------------------------------- |
|
|
60
|
-
| `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`.
|
|
61
|
-
| `provenance: "syntax"` | Syntax or AST structure without semantic symbol resolution.
|
|
62
|
-
| `provenance: "heuristic"` | A convention or name-based suggestion.
|
|
63
|
-
| index evidence | A derived query accelerator, not compiler authority. The
|
|
58
|
+
| Label | Meaning | Safe use |
|
|
59
|
+
| ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `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. |
|
|
61
|
+
| `provenance: "syntax"` | Syntax or AST structure without semantic symbol resolution. | Navigation and structural context only; not proof that two symbols are related. |
|
|
62
|
+
| `provenance: "heuristic"` | A convention or name-based suggestion. | Discovery hints only; never mutation authority or a compiler-backed test candidate. |
|
|
63
|
+
| 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. |
|
|
64
64
|
|
|
65
65
|
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.
|
|
66
66
|
|
|
@@ -84,7 +84,7 @@ 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
|
-
|
|
87
|
+
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. 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
|
|
|
@@ -100,7 +100,7 @@ The parent waits for child readiness before replaying bounded initialization sta
|
|
|
100
100
|
|
|
101
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
102
|
|
|
103
|
-
In published v0.
|
|
103
|
+
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.
|
|
104
104
|
|
|
105
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:
|
|
106
106
|
|
|
@@ -261,7 +261,7 @@ Claude Code supports local stdio MCP servers. After building this repository, re
|
|
|
261
261
|
|
|
262
262
|
```bash
|
|
263
263
|
AST_MCP_DIR="$(pwd)"
|
|
264
|
-
claude mcp add --scope user --transport stdio ast -- \
|
|
264
|
+
claude mcp add --scope user --env AST_MCP_APPLY_GUARD=allow --transport stdio ast -- \
|
|
265
265
|
node "$AST_MCP_DIR/dist/index.js"
|
|
266
266
|
claude mcp get ast
|
|
267
267
|
```
|
|
@@ -316,12 +316,44 @@ Use `/mcp` inside Claude Code to inspect server status and tools. Outside the se
|
|
|
316
316
|
Hermes Agent:
|
|
317
317
|
|
|
318
318
|
```bash
|
|
319
|
-
hermes mcp add ast --command node --args /absolute/path/to/ast-mcp-server/dist/index.js
|
|
319
|
+
hermes mcp add ast --command node --env AST_MCP_APPLY_GUARD=allow --args /absolute/path/to/ast-mcp-server/dist/index.js
|
|
320
320
|
hermes mcp test ast
|
|
321
321
|
```
|
|
322
322
|
|
|
323
323
|
Project-scoped tools accept `project_root`, either the project directory or an explicit `tsconfig.json` path. The server contains no repository-specific paths.
|
|
324
324
|
|
|
325
|
+
### DeepSeek Harness (Developer Preview)
|
|
326
|
+
|
|
327
|
+
A thin adapter ships inside this package: `cordis.patch.yml` mounts the packaged
|
|
328
|
+
`ast-mcp-server` stdio command through the official
|
|
329
|
+
[`@deepseek-ai/dsh-mcp-client`](https://github.com/deepseek-ai/deepseek-harness/blob/cd5ef8148158c3a752a658978873241fdf8e2bbc/packages/mcp/mcp-client/README.md)
|
|
330
|
+
bridge, declared through exactly `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`.
|
|
331
|
+
This is a Developer Preview against a pinned, **source-built** Harness revision
|
|
332
|
+
(`dsh-v0.1.2-alpha.1` at `cd5ef8148158c3a752a658978873241fdf8e2bbc`); that revision
|
|
333
|
+
is validated from source, and `ast-mcp-server@0.13.0` is not published yet. Install a
|
|
334
|
+
locally packed tarball instead:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
yarn pack --out ast-mcp-server-%v.tgz
|
|
338
|
+
dsh plugin --profile web add ./ast-mcp-server-0.13.0.tgz
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The first supported surface is **reads + prepare + preview**. Every apply path is
|
|
342
|
+
denied by a fail-closed guard: `ast_apply_operation` is not registered unless
|
|
343
|
+
`AST_MCP_APPLY_GUARD=allow` is set explicitly (the shipped patch instead pins `deny`,
|
|
344
|
+
so the Harness surface stays deny-by-default; an unset or invalid value also denies). Known upstream
|
|
345
|
+
gaps apply and are never safety authority: the official bridge drops MCP tool
|
|
346
|
+
annotations (`readOnlyHint`/`destructiveHint`) and launches the stdio child outside
|
|
347
|
+
the Harness sandbox.
|
|
348
|
+
|
|
349
|
+
`yarn test:dsh-adapter` is the mandatory verification: it packs the exact tarball,
|
|
350
|
+
installs it into an isolated profile on the pinned Harness, proves `--dump-config`
|
|
351
|
+
composition including effective `tools.mode: native`, and — through the Harness registry
|
|
352
|
+
itself — invokes the server-qualified read, dry-run rename prepare, and preview tools.
|
|
353
|
+
It separately proves `mcp__ast__ast_apply_operation` is absent and that a bounded direct
|
|
354
|
+
registry invocation is rejected. The smoke fails (never skips) if the pinned Harness
|
|
355
|
+
revision, tag, CLI/client version, clean source identity, or a prerequisite is missing.
|
|
356
|
+
|
|
325
357
|
## MCP tools
|
|
326
358
|
|
|
327
359
|
| Tool | Purpose | Mutates files |
|
|
@@ -345,6 +377,12 @@ Project-scoped tools accept `project_root`, either the project directory or an e
|
|
|
345
377
|
|
|
346
378
|
Read results use project-relative paths, deterministic ordering, structured MCP output, and pagination where result sets can grow with the project.
|
|
347
379
|
|
|
380
|
+
### Diagnostic aggregates
|
|
381
|
+
|
|
382
|
+
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.
|
|
383
|
+
|
|
384
|
+
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.
|
|
385
|
+
|
|
348
386
|
`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.
|
|
349
387
|
|
|
350
388
|
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).
|
|
@@ -486,7 +524,19 @@ The server does **not** claim a filesystem-wide transaction:
|
|
|
486
524
|
|
|
487
525
|
## Development gates
|
|
488
526
|
|
|
527
|
+
### Add or change an MCP tool
|
|
528
|
+
|
|
529
|
+
Keep schemas, metadata, annotations, handlers, errors, and serialization in the tool module. Export
|
|
530
|
+
one frozen descriptor, add it to the intentional order in `src/tools/catalog.ts`, and declare only
|
|
531
|
+
static effect, batch, compatibility, and direct-format facts. Do not add request state, dynamic
|
|
532
|
+
discovery, invocation-by-name, or a generic executor to the catalog.
|
|
533
|
+
|
|
534
|
+
Update the independent `tools/list` inventories only after reviewing the complete wire metadata;
|
|
535
|
+
they must not import or derive expectations from the catalog. Then run the focused catalog and MCP
|
|
536
|
+
integration tests plus the runtime, package, and managed-skill gates below.
|
|
537
|
+
|
|
489
538
|
```bash
|
|
539
|
+
yarn vitest run test/tool-catalog.test.ts test/mcp.integration.test.ts
|
|
490
540
|
yarn format:check
|
|
491
541
|
yarn lint
|
|
492
542
|
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.
|
|
14
|
+
This project supports trusted single-user local stdio use; the supported operating-system and architecture boundary remains Linux x64 only. Published v0.12.0 requires Node.js `>=22.13.0`, with immutable evidence targeted at exact Node.js 22.13.0 and the governed Node.js 24 major. 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 release'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
|
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# DeepSeek Harness adapter patch for ast-mcp-server (roadmap initiative 4).
|
|
2
|
+
#
|
|
3
|
+
# Mounts the packaged `ast-mcp-server` stdio command through the official
|
|
4
|
+
# `@deepseek-ai/dsh-mcp-client` bridge. Harness does not resolve MCP binaries
|
|
5
|
+
# from PATH, so the entrypoint is resolved relative to the profile directory
|
|
6
|
+
# (`baseUrl`), where `dsh plugin --profile <name> add ./ast-mcp-server-0.13.0.tgz`
|
|
7
|
+
# installs a locally packed package as a dependency.
|
|
8
|
+
#
|
|
9
|
+
# First-slice surface: reads + prepare + preview. Every apply path is denied
|
|
10
|
+
# by the server-side guard activated through `AST_MCP_APPLY_GUARD=deny`.
|
|
11
|
+
#
|
|
12
|
+
# Known upstream gaps (documented, never safety authority):
|
|
13
|
+
# - the official bridge drops MCP tool.annotations, incl. readOnlyHint and
|
|
14
|
+
# destructiveHint, so the guard enumerates tool names instead of hints;
|
|
15
|
+
# - the stdio child runs outside the Harness sandbox.
|
|
16
|
+
- insert:
|
|
17
|
+
- id: mcp-ast
|
|
18
|
+
name: "@deepseek-ai/dsh-mcp-client"
|
|
19
|
+
config:
|
|
20
|
+
serverName: ast
|
|
21
|
+
transport: stdio
|
|
22
|
+
command: !!js process.execPath
|
|
23
|
+
args:
|
|
24
|
+
- !!js >-
|
|
25
|
+
process.getBuiltinModule('node:url').fileURLToPath(
|
|
26
|
+
new URL('node_modules/ast-mcp-server/dist/index.js', baseUrl)
|
|
27
|
+
)
|
|
28
|
+
env:
|
|
29
|
+
AST_MCP_APPLY_GUARD: "deny"
|
|
30
|
+
cwd: !!js process.cwd()
|
|
31
|
+
failOnStartupError: true
|
package/dist/batch/schema.js
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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({
|
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 {
|
|
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,11 @@ export function createServer(options = {}) {
|
|
|
40
25
|
});
|
|
41
26
|
if (options.runtimeActivity)
|
|
42
27
|
installRuntimeActivityTracking(server, options.runtimeActivity);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
if (options.denyApply === undefined) {
|
|
29
|
+
toolCatalog.registerAll(server);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
toolCatalog.registerAll(server, { denyApply: options.denyApply });
|
|
33
|
+
}
|
|
59
34
|
return server;
|
|
60
35
|
}
|
|
@@ -9,7 +9,7 @@ import { applySkillInstallationPlan, planBundledSkillInstallation, SkillConflict
|
|
|
9
9
|
import { planManagedGuidance, } from "./managed-guidance.js";
|
|
10
10
|
import { applyManagedFilePlan, createManagedFileApplyContext, ManagedFileApplyError, ManagedFileRollbackError, revalidateManagedFilePlan, verifyManagedFilePostimage, } from "./managed-file.js";
|
|
11
11
|
import { AGENT_IDS, classifyAgentVersion, getAgentTarget, MCP_SERVER_NAME, } from "./agent-targets.js";
|
|
12
|
-
import { applyOpenCodeConfigPlan, planOpenCodeConfig, resolveOpenCodeConfigPath, withIsolatedOpenCodeConfig, } from "./opencode-config.js";
|
|
12
|
+
import { applyOpenCodeConfigPlan, classifyOpenCodeAstRegistration, planOpenCodeConfig, resolveOpenCodeConfigPath, OpenCodeConfigRecoveryError, runOpenCodeConfigTransaction, withIsolatedOpenCodeConfig, } from "./opencode-config.js";
|
|
13
13
|
const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
14
14
|
const MAX_COMMAND_OUTPUT_BYTES = 1024 * 1024;
|
|
15
15
|
export class AgentSetupError extends Error {
|
|
@@ -238,10 +238,12 @@ async function verifyOpenCode(detection, environment, timeoutMs, context) {
|
|
|
238
238
|
throw new AgentSetupError("OpenCode resolved configuration output is unknown.", "AGENT_VERIFICATION_FAILED", { agent: "opencode" });
|
|
239
239
|
}
|
|
240
240
|
const command = config?.mcp?.ast?.command;
|
|
241
|
+
const guard = config?.mcp?.ast?.environment?.AST_MCP_APPLY_GUARD;
|
|
241
242
|
if (!Array.isArray(command) ||
|
|
242
243
|
command.length !== 2 ||
|
|
243
244
|
command[0] !== context.nodeExecutable ||
|
|
244
|
-
command[1] !== context.serverEntryPath
|
|
245
|
+
command[1] !== context.serverEntryPath ||
|
|
246
|
+
guard !== "allow") {
|
|
245
247
|
throw new AgentSetupError("OpenCode resolved configuration does not contain the expected ast server.", "AGENT_VERIFICATION_FAILED", { agent: "opencode" });
|
|
246
248
|
}
|
|
247
249
|
const listed = await runtime.run(["mcp", "list", "--pure"]);
|
|
@@ -265,6 +267,63 @@ async function configureMcp(agent, detection, serverEntryPath, nodeExecutable, e
|
|
|
265
267
|
throw new AgentSetupError(`${target.label} saved the MCP registration but verification did not match.`, "AGENT_VERIFICATION_FAILED", { agent, inspection: verified });
|
|
266
268
|
}
|
|
267
269
|
}
|
|
270
|
+
async function repairMcp(agent, detection, serverEntryPath, nodeExecutable, environment, timeoutMs) {
|
|
271
|
+
const target = getAgentTarget(agent);
|
|
272
|
+
const runtime = targetRuntime(detection, environment, timeoutMs);
|
|
273
|
+
const context = { serverEntryPath, nodeExecutable };
|
|
274
|
+
const authenticated = await target.mcp.inspect(runtime, context);
|
|
275
|
+
if (authenticated.status !== "repairable") {
|
|
276
|
+
throw new AgentSetupError(`${target.label} legacy registration changed before repair and was not removed.`, "AGENT_MCP_CONFLICT", { agent, inspection: authenticated });
|
|
277
|
+
}
|
|
278
|
+
const recover = async (cause) => {
|
|
279
|
+
const durable = await target.mcp.inspect(runtime, context);
|
|
280
|
+
if (durable.status === "current")
|
|
281
|
+
return;
|
|
282
|
+
if (durable.status === "repairable")
|
|
283
|
+
throw cause;
|
|
284
|
+
if (durable.status !== "missing") {
|
|
285
|
+
throw new AgentSetupError(`${target.label} migration outcome conflicts with the authenticated registration; it was preserved.`, "AGENT_MCP_CONFLICT", { agent, inspection: durable });
|
|
286
|
+
}
|
|
287
|
+
// The authenticated preimage is absent. Add it directly: never unregister
|
|
288
|
+
// here, because a concurrent replacement must make this add fail harmlessly.
|
|
289
|
+
const restored = await target.mcp.registerLegacy(runtime, context);
|
|
290
|
+
const afterRestore = await target.mcp.inspect(runtime, context);
|
|
291
|
+
if (afterRestore.status === "current")
|
|
292
|
+
return;
|
|
293
|
+
if (afterRestore.status === "repairable")
|
|
294
|
+
throw cause;
|
|
295
|
+
if (!target.mcp.registrationAccepted(restored) && afterRestore.status === "conflict") {
|
|
296
|
+
throw new AgentSetupError(`${target.label} changed concurrently during migration recovery; the replacement was preserved.`, "AGENT_MCP_CONFLICT", { agent, inspection: afterRestore });
|
|
297
|
+
}
|
|
298
|
+
throw new AgentSetupError(`${target.label} migration failed and its authenticated legacy registration is missing.`, "AGENT_VERIFICATION_FAILED", { agent, cause: cause instanceof Error ? cause.message : String(cause), afterRestore });
|
|
299
|
+
};
|
|
300
|
+
try {
|
|
301
|
+
let removalError;
|
|
302
|
+
try {
|
|
303
|
+
const removed = await target.mcp.unregister(runtime);
|
|
304
|
+
if (!target.mcp.removalAccepted(removed)) {
|
|
305
|
+
removalError = commandFailure(agent, "legacy MCP removal", removed);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
removalError = error;
|
|
310
|
+
}
|
|
311
|
+
if (removalError !== undefined) {
|
|
312
|
+
return recover(removalError);
|
|
313
|
+
}
|
|
314
|
+
const added = await target.mcp.register(runtime, context);
|
|
315
|
+
if (!target.mcp.registrationAccepted(added)) {
|
|
316
|
+
throw commandFailure(agent, "MCP registration repair", added);
|
|
317
|
+
}
|
|
318
|
+
const verified = await target.mcp.inspect(runtime, context);
|
|
319
|
+
if (verified.status !== "current") {
|
|
320
|
+
throw new AgentSetupError(`${target.label} saved the repaired MCP registration but verification did not match.`, "AGENT_VERIFICATION_FAILED", { agent, inspection: verified });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
return recover(error);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
268
327
|
async function assertRegularFile(filePath, label) {
|
|
269
328
|
let fileStat;
|
|
270
329
|
try {
|
|
@@ -367,16 +426,11 @@ export async function runAgentSetup(options) {
|
|
|
367
426
|
catch {
|
|
368
427
|
throw new AgentSetupError("OpenCode effective configuration output is unknown.", "AGENT_MCP_CONFLICT", { agent: "opencode", correlation_id: correlationId });
|
|
369
428
|
}
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
type: "local",
|
|
373
|
-
command: [nodeExecutable, options.serverEntryPath],
|
|
374
|
-
enabled: true,
|
|
375
|
-
};
|
|
376
|
-
if (effectiveAst !== undefined && JSON.stringify(effectiveAst) !== JSON.stringify(desiredAst)) {
|
|
429
|
+
const effectiveAstStatus = classifyOpenCodeAstRegistration(effectiveConfig?.mcp?.ast, nodeExecutable, options.serverEntryPath);
|
|
430
|
+
if (effectiveAstStatus === "conflict") {
|
|
377
431
|
throw new AgentSetupError("OpenCode effective configuration conflict at mcp.ast.", "AGENT_MCP_CONFLICT", { agent: "opencode", correlation_id: correlationId });
|
|
378
432
|
}
|
|
379
|
-
if (
|
|
433
|
+
if (effectiveAstStatus === "current" && openCodePlan.status === "installed") {
|
|
380
434
|
openCodePlan = { ...openCodePlan, status: "unchanged" };
|
|
381
435
|
}
|
|
382
436
|
}
|
|
@@ -434,23 +488,35 @@ export async function runAgentSetup(options) {
|
|
|
434
488
|
const inspection = inspections.find((item) => item.agent === agent);
|
|
435
489
|
try {
|
|
436
490
|
await verifyManagedAssetPlans(skillPlan, guidancePlan, applyContext);
|
|
437
|
-
if (inspection.status === "missing") {
|
|
491
|
+
if (inspection.status === "missing" || inspection.status === "repairable") {
|
|
438
492
|
if (agent === "opencode") {
|
|
439
|
-
|
|
440
|
-
|
|
493
|
+
try {
|
|
494
|
+
await runOpenCodeConfigTransaction(openCodePlan, async () => {
|
|
495
|
+
await applyOpenCodeConfigPlan(openCodePlan);
|
|
496
|
+
await verifyOpenCode(detection, environment, timeoutMs, {
|
|
497
|
+
nodeExecutable,
|
|
498
|
+
serverEntryPath: options.serverEntryPath,
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
if (error instanceof OpenCodeConfigRecoveryError)
|
|
504
|
+
throw new AgentSetupError("OpenCode configuration outcome could not be restored safely.", "AGENT_VERIFICATION_FAILED", { cause: error.message });
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
507
|
+
if (openCodePlan.status === "installed")
|
|
441
508
|
physicalWrites.push({
|
|
442
509
|
path: openCodePlan.filePath,
|
|
443
510
|
asset: "mcp_config",
|
|
444
511
|
status: "installed",
|
|
445
512
|
});
|
|
446
|
-
}
|
|
447
|
-
await verifyOpenCode(detection, environment, timeoutMs, {
|
|
448
|
-
nodeExecutable,
|
|
449
|
-
serverEntryPath: options.serverEntryPath,
|
|
450
|
-
});
|
|
451
513
|
}
|
|
452
|
-
else
|
|
514
|
+
else if (inspection.status === "repairable") {
|
|
515
|
+
await repairMcp(agent, detection, options.serverEntryPath, nodeExecutable, environment, timeoutMs);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
453
518
|
await configureMcp(agent, detection, options.serverEntryPath, nodeExecutable, environment, timeoutMs);
|
|
519
|
+
}
|
|
454
520
|
}
|
|
455
521
|
else if (agent === "opencode") {
|
|
456
522
|
await verifyOpenCode(detection, environment, timeoutMs, {
|