ast-mcp-server 0.6.0 → 0.7.1
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 +38 -0
- package/README.md +47 -15
- package/SECURITY.md +31 -0
- package/dist/cli.js +0 -0
- package/dist/index.js +129 -10
- package/dist/server.js +21 -2
- package/dist/services/agent-setup.js +36 -117
- package/dist/services/agent-targets.js +122 -0
- package/dist/services/context-builder.js +52 -24
- package/dist/services/diagnostics.js +10 -5
- package/dist/services/file-fingerprints.js +104 -0
- package/dist/services/file-snapshot.js +8 -1
- package/dist/services/impact.js +334 -0
- package/dist/services/operations.js +343 -254
- package/dist/services/outline.js +94 -21
- package/dist/services/pagination.js +10 -4
- package/dist/services/project-operation-scheduler.js +475 -0
- package/dist/services/project-status.js +134 -52
- package/dist/services/project-watcher.js +230 -0
- package/dist/services/project.js +646 -66
- package/dist/services/public-errors.js +255 -0
- package/dist/services/references.js +7 -1
- package/dist/services/relationships.js +1198 -0
- package/dist/services/request-context.js +42 -0
- package/dist/services/runtime-activity.js +65 -0
- package/dist/services/runtime-logger.js +50 -0
- package/dist/services/runtime-policy.js +47 -0
- package/dist/services/setup-wizard.js +4 -7
- package/dist/services/shutdown.js +93 -0
- package/dist/services/symbol-index-limits.js +5 -0
- package/dist/services/symbol-index-order.js +13 -0
- package/dist/services/symbol-index-policy.js +91 -0
- package/dist/services/symbol-index-sqlite.js +850 -0
- package/dist/services/symbol-index.js +447 -0
- package/dist/services/symbols.js +403 -79
- package/dist/services/test-candidates.js +209 -0
- package/dist/services/workspace.js +18 -7
- package/dist/tools/apply_operation.js +8 -4
- package/dist/tools/explore.js +8 -6
- package/dist/tools/find_references.js +12 -13
- package/dist/tools/get_diagnostics.js +10 -7
- package/dist/tools/get_file.js +23 -11
- package/dist/tools/get_impact.js +134 -0
- package/dist/tools/get_operation_preview.js +7 -4
- package/dist/tools/get_outline.js +8 -6
- package/dist/tools/get_project_status.js +77 -8
- package/dist/tools/get_symbol_source.js +8 -6
- package/dist/tools/list_files.js +11 -6
- package/dist/tools/rename_symbol.js +6 -4
- package/dist/tools/replace_symbol_body.js +6 -4
- package/dist/tools/result.js +27 -17
- package/dist/tools/scaffold_class.js +6 -4
- package/dist/tools/search_symbols.js +15 -14
- package/docs/support.md +80 -0
- package/package.json +16 -5
- package/skills/structural-code-editing/SKILL.md +37 -10
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getOperationPreview } from "../services/operations.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
4
|
+
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
4
5
|
const AstGetOperationPreviewInputSchema = z.object({
|
|
5
6
|
operation_id: z.string().uuid(),
|
|
6
7
|
file: z.string().optional().describe("Optional affected file path to retrieve only its diff."),
|
|
@@ -22,12 +23,14 @@ export function registerGetOperationPreview(server) {
|
|
|
22
23
|
idempotentHint: true,
|
|
23
24
|
openWorldHint: false,
|
|
24
25
|
},
|
|
25
|
-
}, async ({ operation_id, file }) => {
|
|
26
|
+
}, async ({ operation_id, file }, extra) => {
|
|
27
|
+
const requestContext = createRequestContext(extra.signal);
|
|
26
28
|
try {
|
|
27
|
-
|
|
29
|
+
const output = await getOperationPreview(operation_id, file, requestContext);
|
|
30
|
+
return structuredResult(output);
|
|
28
31
|
}
|
|
29
32
|
catch (error) {
|
|
30
|
-
return errorResult(error);
|
|
33
|
+
return errorResult(error, createToolErrorContext("ast_get_operation_preview"));
|
|
31
34
|
}
|
|
32
35
|
});
|
|
33
36
|
}
|
|
@@ -2,7 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { buildFileOutline } from "../services/outline.js";
|
|
4
4
|
import { getSourceFileOrThrow, withProject } from "../services/project.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
6
|
+
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
6
7
|
const AstGetOutlineInputSchema = z.object({
|
|
7
8
|
project_root: z
|
|
8
9
|
.string()
|
|
@@ -38,21 +39,22 @@ export function registerGetOutline(server) {
|
|
|
38
39
|
idempotentHint: true,
|
|
39
40
|
openWorldHint: false,
|
|
40
41
|
},
|
|
41
|
-
}, async ({ project_root, file_path, include_symbols }) => {
|
|
42
|
+
}, async ({ project_root, file_path, include_symbols }, extra) => {
|
|
43
|
+
const requestContext = createRequestContext(extra.signal);
|
|
42
44
|
try {
|
|
43
|
-
const structuredContent = await withProject(project_root, ({ project, projectRoot }) => {
|
|
45
|
+
const structuredContent = await withProject(project_root, ({ project, projectRoot }, operationContext) => {
|
|
44
46
|
const sourceFile = getSourceFileOrThrow(project, file_path);
|
|
45
|
-
const outline = buildFileOutline(sourceFile);
|
|
47
|
+
const outline = buildFileOutline(sourceFile, operationContext);
|
|
46
48
|
return {
|
|
47
49
|
file: path.relative(projectRoot, sourceFile.getFilePath()),
|
|
48
50
|
outline: outline.text,
|
|
49
51
|
...(include_symbols ? { symbols: outline.symbols } : {}),
|
|
50
52
|
};
|
|
51
|
-
});
|
|
53
|
+
}, requestContext);
|
|
52
54
|
return structuredResult(structuredContent);
|
|
53
55
|
}
|
|
54
56
|
catch (error) {
|
|
55
|
-
return errorResult(error);
|
|
57
|
+
return errorResult(error, createToolErrorContext("ast_get_outline", project_root));
|
|
56
58
|
}
|
|
57
59
|
});
|
|
58
60
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { FRESHNESS_CAUSES, SNAPSHOT_STATES, TRUNCATION_REASONS, } from "../services/read-contracts.js";
|
|
3
3
|
import { getProjectStatus } from "../services/project.js";
|
|
4
|
-
import {
|
|
4
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
5
|
+
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
5
6
|
const AstGetProjectStatusInputSchema = z.object({
|
|
6
7
|
project_root: z
|
|
7
8
|
.string()
|
|
@@ -11,6 +12,54 @@ const TruncationMetadataSchema = z.object({
|
|
|
11
12
|
truncated: z.boolean(),
|
|
12
13
|
reason: z.enum(TRUNCATION_REASONS).nullable(),
|
|
13
14
|
});
|
|
15
|
+
const IndexObservabilitySchema = z.object({
|
|
16
|
+
policy: z.enum(["disabled", "canary", "enabled"]),
|
|
17
|
+
policy_reason: z.enum([
|
|
18
|
+
"default",
|
|
19
|
+
"invalid_mode",
|
|
20
|
+
"enabled_not_released",
|
|
21
|
+
"cache_root_missing",
|
|
22
|
+
"cache_root_invalid",
|
|
23
|
+
]),
|
|
24
|
+
backend: z.enum(["memory", "sqlite"]),
|
|
25
|
+
state: z.enum(["disabled", "ready", "rebuilding", "failed"]),
|
|
26
|
+
operation: z.enum([
|
|
27
|
+
"disabled",
|
|
28
|
+
"hit",
|
|
29
|
+
"miss",
|
|
30
|
+
"rebuild",
|
|
31
|
+
"fallback",
|
|
32
|
+
"migration",
|
|
33
|
+
"corruption",
|
|
34
|
+
"read_failure",
|
|
35
|
+
"write_failure",
|
|
36
|
+
]),
|
|
37
|
+
last_operation: z.enum([
|
|
38
|
+
"disabled",
|
|
39
|
+
"hit",
|
|
40
|
+
"miss",
|
|
41
|
+
"rebuild",
|
|
42
|
+
"fallback",
|
|
43
|
+
"migration",
|
|
44
|
+
"corruption",
|
|
45
|
+
"read_failure",
|
|
46
|
+
"write_failure",
|
|
47
|
+
]),
|
|
48
|
+
loaded_entries: z.number().int().min(0),
|
|
49
|
+
accepted_entries: z.number().int().min(0),
|
|
50
|
+
rejected_entries: z.number().int().min(0),
|
|
51
|
+
cache_hits: z.number().int().min(0),
|
|
52
|
+
cache_misses: z.number().int().min(0),
|
|
53
|
+
fallback_count: z.number().int().min(0),
|
|
54
|
+
migration_count: z.number().int().min(0),
|
|
55
|
+
corruption_count: z.number().int().min(0),
|
|
56
|
+
write_failure_count: z.number().int().min(0),
|
|
57
|
+
rebuilt_files: z.number().int().min(0),
|
|
58
|
+
reused_files: z.number().int().min(0),
|
|
59
|
+
removed_files: z.number().int().min(0),
|
|
60
|
+
last_error: z.string().nullable(),
|
|
61
|
+
last_successful_persistence_at: z.string().nullable(),
|
|
62
|
+
});
|
|
14
63
|
const ProjectStatusOutputSchema = z.object({
|
|
15
64
|
project: z.object({
|
|
16
65
|
project_id: z.string().regex(/^project_[0-9a-f]{20}$/),
|
|
@@ -28,21 +77,40 @@ const ProjectStatusOutputSchema = z.object({
|
|
|
28
77
|
pending_files_truncation: TruncationMetadataSchema,
|
|
29
78
|
pending_files_filename_truncation: TruncationMetadataSchema,
|
|
30
79
|
compiler: z.object({ state: z.enum(["ready", "rebuilding", "failed"]) }),
|
|
31
|
-
index: z.object({ state: z.
|
|
80
|
+
index: z.object({ state: z.enum(["disabled", "ready", "rebuilding", "failed"]) }),
|
|
32
81
|
operation_queue: z.object({
|
|
33
82
|
state: z.enum(["idle", "queued", "running"]),
|
|
34
|
-
|
|
35
|
-
|
|
83
|
+
admission: z.enum(["open", "closed"]),
|
|
84
|
+
queue_capacity: z.number().int().min(1).max(256),
|
|
85
|
+
active_operations: z.number().int().min(0).max(1),
|
|
86
|
+
queued_operations: z.number().int().min(0).max(256),
|
|
87
|
+
rejected_operations: z.number().int().min(0).max(2_147_483_647),
|
|
88
|
+
cancelled_operations: z.number().int().min(0).max(2_147_483_647),
|
|
89
|
+
queue_timeout_operations: z.number().int().min(0).max(2_147_483_647),
|
|
90
|
+
deadline_exceeded_operations: z.number().int().min(0).max(2_147_483_647),
|
|
91
|
+
last_outcome: z.enum([
|
|
92
|
+
"none",
|
|
93
|
+
"succeeded",
|
|
94
|
+
"failed",
|
|
95
|
+
"rejected",
|
|
96
|
+
"cancelled",
|
|
97
|
+
"queue_timeout",
|
|
98
|
+
"deadline_exceeded",
|
|
99
|
+
"internal_error",
|
|
100
|
+
]),
|
|
101
|
+
max_queue_wait_ms: z.number().int().min(0).max(86_400_000),
|
|
102
|
+
max_execution_ms: z.number().int().min(0).max(86_400_000),
|
|
36
103
|
}),
|
|
37
104
|
watcher: z.object({ state: z.enum(["disabled", "ready", "failed"]) }),
|
|
38
105
|
last_successful_sync_at: z.string().nullable(),
|
|
39
|
-
last_successful_index_at: z.
|
|
106
|
+
last_successful_index_at: z.string().nullable(),
|
|
40
107
|
source_snapshot_fingerprint: z.string().nullable(),
|
|
41
108
|
config_snapshot_fingerprint: z.string().nullable(),
|
|
42
109
|
canonical_snapshot_fingerprint: z.string().nullable(),
|
|
43
110
|
degraded_errors: z.array(z.string()),
|
|
44
111
|
degraded_errors_truncation: TruncationMetadataSchema,
|
|
45
112
|
degraded_errors_text_truncation: TruncationMetadataSchema,
|
|
113
|
+
index_observability: IndexObservabilitySchema,
|
|
46
114
|
});
|
|
47
115
|
export function registerGetProjectStatus(server) {
|
|
48
116
|
server.registerTool("ast_get_project_status", {
|
|
@@ -56,13 +124,14 @@ export function registerGetProjectStatus(server) {
|
|
|
56
124
|
idempotentHint: true,
|
|
57
125
|
openWorldHint: false,
|
|
58
126
|
},
|
|
59
|
-
}, async ({ project_root }) => {
|
|
127
|
+
}, async ({ project_root }, extra) => {
|
|
128
|
+
const requestContext = createRequestContext(extra.signal);
|
|
60
129
|
try {
|
|
61
|
-
const output = await getProjectStatus(project_root);
|
|
130
|
+
const output = await getProjectStatus(project_root, requestContext);
|
|
62
131
|
return structuredResult({ ...output });
|
|
63
132
|
}
|
|
64
133
|
catch (error) {
|
|
65
|
-
return errorResult(error);
|
|
134
|
+
return errorResult(error, createToolErrorContext("ast_get_project_status", project_root));
|
|
66
135
|
}
|
|
67
136
|
});
|
|
68
137
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { nodeSourceWithLocation } from "../services/outline.js";
|
|
3
3
|
import { findDeclarationByName, getSourceFileOrThrow, withProject } from "../services/project.js";
|
|
4
|
-
import {
|
|
4
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
5
|
+
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
5
6
|
const AstGetSymbolSourceInputSchema = z.object({
|
|
6
7
|
project_root: z
|
|
7
8
|
.string()
|
|
@@ -27,17 +28,18 @@ export function registerGetSymbolSource(server) {
|
|
|
27
28
|
idempotentHint: true,
|
|
28
29
|
openWorldHint: false,
|
|
29
30
|
},
|
|
30
|
-
}, async ({ project_root, file_path, symbol_path }) => {
|
|
31
|
+
}, async ({ project_root, file_path, symbol_path }, extra) => {
|
|
32
|
+
const requestContext = createRequestContext(extra.signal);
|
|
31
33
|
try {
|
|
32
|
-
const structuredContent = await withProject(project_root, ({ project, projectRoot }) => {
|
|
34
|
+
const structuredContent = await withProject(project_root, ({ project, projectRoot }, operationContext) => {
|
|
33
35
|
const sourceFile = getSourceFileOrThrow(project, file_path);
|
|
34
36
|
const node = findDeclarationByName(sourceFile, symbol_path);
|
|
35
|
-
return nodeSourceWithLocation(node, projectRoot);
|
|
36
|
-
});
|
|
37
|
+
return nodeSourceWithLocation(node, projectRoot, operationContext);
|
|
38
|
+
}, requestContext);
|
|
37
39
|
return structuredResult(structuredContent);
|
|
38
40
|
}
|
|
39
41
|
catch (error) {
|
|
40
|
-
return errorResult(error);
|
|
42
|
+
return errorResult(error, createToolErrorContext("ast_get_symbol_source", project_root));
|
|
41
43
|
}
|
|
42
44
|
});
|
|
43
45
|
}
|
package/dist/tools/list_files.js
CHANGED
|
@@ -2,7 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { PaginationInputSchema, PaginationOutputSchema, paginate } from "../services/pagination.js";
|
|
4
4
|
import { withProject } from "../services/project.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
6
|
+
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
6
7
|
const AstListFilesInputSchema = z.object({
|
|
7
8
|
project_root: z
|
|
8
9
|
.string()
|
|
@@ -29,23 +30,27 @@ export function registerListFiles(server) {
|
|
|
29
30
|
idempotentHint: true,
|
|
30
31
|
openWorldHint: false,
|
|
31
32
|
},
|
|
32
|
-
}, async ({ project_root, glob_filter, offset, limit }) => {
|
|
33
|
+
}, async ({ project_root, glob_filter, offset, limit }, extra) => {
|
|
34
|
+
const requestContext = createRequestContext(extra.signal);
|
|
33
35
|
try {
|
|
34
|
-
const output = await withProject(project_root, ({ project, projectRoot }) => {
|
|
36
|
+
const output = await withProject(project_root, ({ project, projectRoot }, operationContext) => {
|
|
35
37
|
const filter = glob_filter?.toLowerCase();
|
|
36
38
|
const files = project
|
|
37
39
|
.getSourceFiles()
|
|
38
|
-
.map((sourceFile) =>
|
|
40
|
+
.map((sourceFile) => {
|
|
41
|
+
operationContext.checkpoint();
|
|
42
|
+
return path.relative(projectRoot, sourceFile.getFilePath());
|
|
43
|
+
})
|
|
39
44
|
.filter((file) => !filter || file.toLowerCase().includes(filter))
|
|
40
45
|
.sort((left, right) => left.localeCompare(right));
|
|
41
46
|
const page = paginate(files, offset, limit);
|
|
42
47
|
const { items, ...metadata } = page;
|
|
43
48
|
return { files: items, ...metadata };
|
|
44
|
-
});
|
|
49
|
+
}, requestContext);
|
|
45
50
|
return structuredResult(output);
|
|
46
51
|
}
|
|
47
52
|
catch (error) {
|
|
48
|
-
return errorResult(error);
|
|
53
|
+
return errorResult(error, createToolErrorContext("ast_list_files", project_root));
|
|
49
54
|
}
|
|
50
55
|
});
|
|
51
56
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { prepareRename } from "../services/operations.js";
|
|
3
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
3
4
|
import { PreparedOperationOutputSchema, serializePreparedOperation } from "./operation-schema.js";
|
|
4
|
-
import { errorResult, structuredResult } from "./result.js";
|
|
5
|
+
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
5
6
|
const AstRenameSymbolInputSchema = z.object({
|
|
6
7
|
project_root: z.string(),
|
|
7
8
|
file_path: z.string(),
|
|
@@ -28,7 +29,8 @@ export function registerRenameSymbol(server) {
|
|
|
28
29
|
idempotentHint: false,
|
|
29
30
|
openWorldHint: false,
|
|
30
31
|
},
|
|
31
|
-
}, async ({ project_root, file_path, symbol_path, new_name, dry_run, allow_new_errors }) => {
|
|
32
|
+
}, async ({ project_root, file_path, symbol_path, new_name, dry_run, allow_new_errors }, extra) => {
|
|
33
|
+
const requestContext = createRequestContext(extra.signal);
|
|
32
34
|
try {
|
|
33
35
|
if (!dry_run) {
|
|
34
36
|
throw new Error("Direct rename application is disabled. Prepare with dry_run=true, then call ast_apply_operation with the returned operation_id.");
|
|
@@ -39,11 +41,11 @@ export function registerRenameSymbol(server) {
|
|
|
39
41
|
symbolPath: symbol_path,
|
|
40
42
|
newName: new_name,
|
|
41
43
|
allowNewErrors: allow_new_errors,
|
|
42
|
-
});
|
|
44
|
+
}, requestContext);
|
|
43
45
|
return structuredResult(serializePreparedOperation(operation));
|
|
44
46
|
}
|
|
45
47
|
catch (error) {
|
|
46
|
-
return errorResult(error);
|
|
48
|
+
return errorResult(error, createToolErrorContext("ast_rename_symbol", project_root));
|
|
47
49
|
}
|
|
48
50
|
});
|
|
49
51
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { prepareReplaceBody } from "../services/operations.js";
|
|
3
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
3
4
|
import { PreparedOperationOutputSchema, serializePreparedOperation } from "./operation-schema.js";
|
|
4
|
-
import { errorResult, structuredResult } from "./result.js";
|
|
5
|
+
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
5
6
|
const AstReplaceSymbolBodyInputSchema = z.object({
|
|
6
7
|
project_root: z.string(),
|
|
7
8
|
file_path: z.string(),
|
|
@@ -28,7 +29,8 @@ export function registerReplaceSymbolBody(server) {
|
|
|
28
29
|
idempotentHint: false,
|
|
29
30
|
openWorldHint: false,
|
|
30
31
|
},
|
|
31
|
-
}, async ({ project_root, file_path, symbol_path, new_body, dry_run, allow_new_errors }) => {
|
|
32
|
+
}, async ({ project_root, file_path, symbol_path, new_body, dry_run, allow_new_errors }, extra) => {
|
|
33
|
+
const requestContext = createRequestContext(extra.signal);
|
|
32
34
|
try {
|
|
33
35
|
if (!dry_run) {
|
|
34
36
|
throw new Error("Direct body replacement is disabled. Prepare with dry_run=true, then call ast_apply_operation with the returned operation_id.");
|
|
@@ -39,11 +41,11 @@ export function registerReplaceSymbolBody(server) {
|
|
|
39
41
|
symbolPath: symbol_path,
|
|
40
42
|
newBody: new_body,
|
|
41
43
|
allowNewErrors: allow_new_errors,
|
|
42
|
-
});
|
|
44
|
+
}, requestContext);
|
|
43
45
|
return structuredResult(serializePreparedOperation(operation));
|
|
44
46
|
}
|
|
45
47
|
catch (error) {
|
|
46
|
-
return errorResult(error);
|
|
48
|
+
return errorResult(error, createToolErrorContext("ast_replace_symbol_body", project_root));
|
|
47
49
|
}
|
|
48
50
|
});
|
|
49
51
|
}
|
package/dist/tools/result.js
CHANGED
|
@@ -2,7 +2,9 @@ import { Buffer } from "node:buffer";
|
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
3
|
import { decode, encode } from "@toon-format/toon";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
|
|
5
|
+
import { createProjectIdentity } from "../services/project-status.js";
|
|
6
|
+
import { renderPublicError } from "../services/public-errors.js";
|
|
7
|
+
import { emitToolFailureEvent } from "../services/runtime-logger.js";
|
|
6
8
|
export const MAX_TOON_RESULT_BYTES = 10 * 1024 * 1024;
|
|
7
9
|
export const ToolOutputFormatSchema = z.enum(["json", "toon"]).default("json");
|
|
8
10
|
export const ToolOutputFormatInputSchema = {
|
|
@@ -12,6 +14,16 @@ export const ToonResultSchema = z.object({
|
|
|
12
14
|
format: z.literal("toon"),
|
|
13
15
|
data: z.string(),
|
|
14
16
|
});
|
|
17
|
+
export function createToolErrorContext(toolName, projectRoot) {
|
|
18
|
+
if (typeof projectRoot !== "string")
|
|
19
|
+
return { toolName };
|
|
20
|
+
try {
|
|
21
|
+
return { toolName, projectIdentity: createProjectIdentity({ projectRoot }) };
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return { toolName };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
15
27
|
export function structuredResult(structuredContent) {
|
|
16
28
|
return { content: [], structuredContent };
|
|
17
29
|
}
|
|
@@ -30,27 +42,25 @@ export function formattedResult(outputSchema, candidate, outputFormat, maxToonBy
|
|
|
30
42
|
}
|
|
31
43
|
return structuredResult({ format: "toon", data: text });
|
|
32
44
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
export function errorResult(error, context) {
|
|
46
|
+
const { envelope, text } = renderPublicError(error);
|
|
47
|
+
try {
|
|
48
|
+
emitToolFailureEvent({
|
|
49
|
+
correlationId: envelope.error.correlation_id,
|
|
50
|
+
toolName: context.toolName,
|
|
51
|
+
code: envelope.error.code,
|
|
52
|
+
message: envelope.error.message,
|
|
53
|
+
projectIdentity: context.projectIdentity,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// A logging sink failure must not replace the bounded MCP error.
|
|
44
58
|
}
|
|
45
|
-
return prefix + suffix;
|
|
46
|
-
}
|
|
47
|
-
export function errorResult(error) {
|
|
48
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
49
59
|
return {
|
|
50
60
|
content: [
|
|
51
61
|
{
|
|
52
62
|
type: "text",
|
|
53
|
-
text
|
|
63
|
+
text,
|
|
54
64
|
},
|
|
55
65
|
],
|
|
56
66
|
isError: true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { prepareScaffoldClass } from "../services/operations.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
4
|
+
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
4
5
|
import { PreparedOperationOutputSchema, serializePreparedOperation } from "./operation-schema.js";
|
|
5
6
|
const IdentifierSchema = z.string().min(1).max(200);
|
|
6
7
|
const TypeTextSchema = z.string().min(1).max(10_000);
|
|
@@ -121,14 +122,15 @@ export function registerScaffoldClass(server) {
|
|
|
121
122
|
idempotentHint: false,
|
|
122
123
|
openWorldHint: false,
|
|
123
124
|
},
|
|
124
|
-
}, async (args) => {
|
|
125
|
+
}, async (args, extra) => {
|
|
126
|
+
const requestContext = createRequestContext(extra.signal);
|
|
125
127
|
try {
|
|
126
128
|
const prepared = await prepareScaffoldClass({
|
|
127
129
|
projectRoot: args.project_root,
|
|
128
130
|
filePath: args.file_path,
|
|
129
131
|
spec: toSpec(args),
|
|
130
132
|
allowNewErrors: args.allow_new_errors,
|
|
131
|
-
});
|
|
133
|
+
}, requestContext);
|
|
132
134
|
const structuredContent = {
|
|
133
135
|
...serializePreparedOperation(prepared.operation),
|
|
134
136
|
file: prepared.file,
|
|
@@ -139,7 +141,7 @@ export function registerScaffoldClass(server) {
|
|
|
139
141
|
return structuredResult(structuredContent);
|
|
140
142
|
}
|
|
141
143
|
catch (error) {
|
|
142
|
-
return errorResult(error);
|
|
144
|
+
return errorResult(error, createToolErrorContext("ast_scaffold_class", args.project_root));
|
|
143
145
|
}
|
|
144
146
|
});
|
|
145
147
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { createPaginationInputSchema, PaginationOutputSchema
|
|
3
|
-
import { withProject } from "../services/project.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { createPaginationInputSchema, PaginationOutputSchema } from "../services/pagination.js";
|
|
3
|
+
import { withProject, reportSymbolIndexFailure } from "../services/project.js";
|
|
4
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
5
|
+
import { searchProjectSymbolsPage, searchProjectSymbolsPageWithIndex, } from "../services/symbols.js";
|
|
6
|
+
import { createToolErrorContext, errorResult, formattedResult, ToolOutputFormatInputSchema, } from "./result.js";
|
|
6
7
|
const SEARCH_DEFAULT_LIMIT = 20;
|
|
7
8
|
const SearchDetailSchema = z.enum(["selectors", "summary", "full"]).default("summary");
|
|
8
9
|
const AstSearchSymbolsInputSchema = z.object({
|
|
@@ -86,28 +87,28 @@ export function registerSearchSymbols(server) {
|
|
|
86
87
|
idempotentHint: true,
|
|
87
88
|
openWorldHint: false,
|
|
88
89
|
},
|
|
89
|
-
}, async ({ project_root, query, kinds, file_filter, detail, output_format, offset, limit }) => {
|
|
90
|
+
}, async ({ project_root, query, kinds, file_filter, detail, output_format, offset, limit }, extra) => {
|
|
91
|
+
const requestContext = createRequestContext(extra.signal);
|
|
90
92
|
try {
|
|
91
|
-
const structuredContent = await withProject(project_root, (
|
|
93
|
+
const structuredContent = await withProject(project_root, async (context, operationContext) => {
|
|
94
|
+
const { project, projectRoot } = context;
|
|
92
95
|
const startedAt = performance.now();
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
fileFilter: file_filter,
|
|
97
|
-
});
|
|
98
|
-
const page = paginate(matches, offset, limit);
|
|
96
|
+
const page = (await searchProjectSymbolsPageWithIndex(project, projectRoot, context.status.project, context.symbolIndex, context.symbolIndexReady, { query, kinds, fileFilter: file_filter }, offset, limit, async (reason) => {
|
|
97
|
+
await reportSymbolIndexFailure(projectRoot, reason);
|
|
98
|
+
}, operationContext)) ??
|
|
99
|
+
searchProjectSymbolsPage(project, projectRoot, { query, kinds, fileFilter: file_filter }, offset, limit, operationContext);
|
|
99
100
|
const { items, ...metadata } = page;
|
|
100
101
|
return {
|
|
101
102
|
symbols: projectSymbols(detail, items),
|
|
102
103
|
duration_ms: performance.now() - startedAt,
|
|
103
104
|
...metadata,
|
|
104
105
|
};
|
|
105
|
-
});
|
|
106
|
+
}, requestContext);
|
|
106
107
|
const outputSchema = SearchOutputSchemas[detail];
|
|
107
108
|
return formattedResult(outputSchema, structuredContent, output_format);
|
|
108
109
|
}
|
|
109
110
|
catch (error) {
|
|
110
|
-
return errorResult(error);
|
|
111
|
+
return errorResult(error, createToolErrorContext("ast_search_symbols", project_root));
|
|
111
112
|
}
|
|
112
113
|
});
|
|
113
114
|
}
|
package/docs/support.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Support policy
|
|
2
|
+
|
|
3
|
+
This document defines the supported platform, runtime, persistence, and operational boundary for `ast-mcp-server` v0.7.1.
|
|
4
|
+
|
|
5
|
+
## Supported release target
|
|
6
|
+
|
|
7
|
+
| Environment | Status | Contract |
|
|
8
|
+
| ------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| Linux x64 with required GNU coreutils, Node.js 22.5.0 | Supported | Full repository, MCP/CLI/package, lifecycle, mutation, and production-readiness gates. Persistence canary runs require `--experimental-sqlite`. |
|
|
10
|
+
| Linux x64 with required GNU coreutils, current Node.js 24 line | Supported | Full repository, MCP/CLI/package, lifecycle, mutation, and production-readiness gates. |
|
|
11
|
+
| Other Linux architectures or systems without required GNU coreutils | Unverified | Not supported until equivalent architecture and filesystem-publication gates pass. |
|
|
12
|
+
| macOS | Unverified | Not supported until equivalent filesystem, process, package, mutation, and canary gates pass. |
|
|
13
|
+
| Windows | Unverified | Not supported until equivalent filesystem, process, package, mutation, and canary gates pass. |
|
|
14
|
+
| Remote, untrusted, or multi-tenant service use | Unsupported | No network authentication, sandbox, or tenant-isolation boundary is provided. |
|
|
15
|
+
|
|
16
|
+
The package engine floor is Node.js `>=22.5.0`, but release evidence is deliberately narrower: exact Node.js 22.5.0 and the current Node.js 24 line on Linux. A newer version satisfying the engine range is not automatically a verified release target.
|
|
17
|
+
|
|
18
|
+
The checked-evidence freezer requires GNU coreutils `mv` with `--update=none-fail`, `--no-copy`, and `--no-target-directory`. Equivalent publication semantics have not been verified on other operating systems or architectures.
|
|
19
|
+
|
|
20
|
+
## Local stdio trust boundary
|
|
21
|
+
|
|
22
|
+
`ast-mcp-server` is a local stdio process. It inherits the invoking user's filesystem authority, and an MCP client may request any `project_root` or `tsconfig.json` that user can access.
|
|
23
|
+
|
|
24
|
+
The server does not provide:
|
|
25
|
+
|
|
26
|
+
- HTTP or network authentication;
|
|
27
|
+
- filesystem or process sandboxing;
|
|
28
|
+
- privilege separation;
|
|
29
|
+
- tenant isolation;
|
|
30
|
+
- protection from a malicious process running with the same filesystem authority.
|
|
31
|
+
|
|
32
|
+
Operation locks coordinate cooperating same-user processes. They do not stop editors, network filesystems, or hostile same-user writers. Report-set freezer coordination likewise assumes cooperating same-UID processes and cannot defend checked evidence against a malicious process with the same filesystem authority. The freezer guarantees no-replace atomic visibility of the checked directory; it does not claim persistence across sudden power loss because parent-directory `fsync` durability is not established.
|
|
33
|
+
|
|
34
|
+
## Symbol-index persistence
|
|
35
|
+
|
|
36
|
+
The supported default is memory-only indexing. When `AST_SYMBOL_INDEX_PERSISTENCE` is absent, persistence remains disabled and no persistent cache is created.
|
|
37
|
+
|
|
38
|
+
The only candidate-authorized opt-in persistence policy is `canary`; it is not enabled by default in v0.7.1:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
AST_SYMBOL_INDEX_PERSISTENCE=canary \
|
|
42
|
+
AST_SYMBOL_INDEX_CACHE_ROOT=/absolute/isolated/cache/root \
|
|
43
|
+
ast-mcp-server
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The cache root must be explicit and absolute. Use a dedicated local directory with permissions appropriate for the invoking user. Node.js 22.5.0 additionally requires `--experimental-sqlite` for this canary path.
|
|
47
|
+
|
|
48
|
+
`AST_SYMBOL_INDEX_PERSISTENCE=enabled` is reserved and intentionally fails closed to memory-only with `policy_reason: "enabled_not_released"`. Unknown values and invalid/missing canary cache roots also fail closed. Canary evidence authorizes the opt-in capability; it does not authorize global or default activation.
|
|
49
|
+
|
|
50
|
+
## Production-readiness acceptance
|
|
51
|
+
|
|
52
|
+
The checked Linux x64 matrix covers `ast-mcp-server` and `x-scraper` under Node.js 22.5.0 and Node.js 24. MCP-PROD-404 is normative; its complete release gate requires all four reports to pass the following contract:
|
|
53
|
+
|
|
54
|
+
- zero semantic mismatches across disabled, cold, warm, restart, and rollback reads;
|
|
55
|
+
- byte-identical real-repository status and worktree trees;
|
|
56
|
+
- compiler-equivalent corruption and write-failure fallback with explicit recovery;
|
|
57
|
+
- exact mutation postimages, replay, byte-exact rollback, and no cache side effect in disposable fixtures;
|
|
58
|
+
- bounded queue/session behavior and cancellation;
|
|
59
|
+
- one process running identical deterministic-fixture reads under Node.js launched with `--expose-gc`;
|
|
60
|
+
- exactly 10 warmups, 50 measured reads, and one explicit `global.gc()` immediately before every measured RSS sample;
|
|
61
|
+
- deterministic-fixture final-five median RSS no more than `max(32 MiB, 20%)` above the first-five median;
|
|
62
|
+
- immutable-cache growth from the first complete build to the third unchanged restart no more than `max(1 MiB, 5%)`;
|
|
63
|
+
- recursive cache accounting through `lstat` without following symlinks, including SQLite main, WAL, SHM, quarantine, and temporary files in a stable sorted relative-file manifest; any unreadable, symbolic, or non-regular entry fails the gate.
|
|
64
|
+
|
|
65
|
+
Any semantic mismatch, mutation-boundary effect, unrecovered fallback, repository mutation, path/secret disclosure, unexpected or accepted invalid cache entry, or failed runtime/identity check fails the gate. Deliberately injected corruption is valid only when it is rejected, falls back to the exact compiler baseline, and then recovers explicitly.
|
|
66
|
+
|
|
67
|
+
Real-repository latency and RSS are retained as observations only. They are not capacity guarantees, SLAs, or PASS/FAIL criteria for this release.
|
|
68
|
+
|
|
69
|
+
## Compatibility and issue handling
|
|
70
|
+
|
|
71
|
+
Before reporting a compatibility problem, capture:
|
|
72
|
+
|
|
73
|
+
- operating system and architecture;
|
|
74
|
+
- exact `node --version` output and Node binary path;
|
|
75
|
+
- package version;
|
|
76
|
+
- MCP client and transport configuration;
|
|
77
|
+
- whether persistence is disabled or running in explicit canary mode;
|
|
78
|
+
- the bounded public error code and correlation identifier, if present.
|
|
79
|
+
|
|
80
|
+
Do not attach source code, absolute project paths, cache contents, credentials, tokens, or private configuration unless they have been intentionally sanitized. Report suspected vulnerabilities through [SECURITY.md](../SECURITY.md), not a public issue.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ast-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Structural code MCP server and batch CLI for compact TypeScript/JavaScript reads and hash-bound AST edits.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -35,13 +35,16 @@
|
|
|
35
35
|
"dist",
|
|
36
36
|
"skills",
|
|
37
37
|
"README.md",
|
|
38
|
-
"CHANGELOG.md"
|
|
38
|
+
"CHANGELOG.md",
|
|
39
|
+
"SECURITY.md",
|
|
40
|
+
"docs/support.md"
|
|
39
41
|
],
|
|
40
42
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
43
|
+
"node": ">=22.5.0"
|
|
42
44
|
},
|
|
43
45
|
"resolutions": {
|
|
44
|
-
"hono": "4.12.34"
|
|
46
|
+
"hono": "4.12.34",
|
|
47
|
+
"nanoid": "3.3.17"
|
|
45
48
|
},
|
|
46
49
|
"scripts": {
|
|
47
50
|
"build": "tsc",
|
|
@@ -49,14 +52,21 @@
|
|
|
49
52
|
"test": "vitest run",
|
|
50
53
|
"test:watch": "vitest",
|
|
51
54
|
"test:mcp": "yarn build && node scripts/mcp-smoke.mjs",
|
|
55
|
+
"test:lifecycle": "yarn build && node scripts/mcp-lifecycle-smoke.mjs",
|
|
52
56
|
"test:cli": "yarn build && node scripts/cli-smoke.mjs",
|
|
57
|
+
"test:errors": "yarn build && node scripts/public-error-smoke.mjs",
|
|
53
58
|
"test:package": "node scripts/package-smoke.mjs",
|
|
59
|
+
"test:release-candidate": "node scripts/release-candidate-matrix.mjs",
|
|
54
60
|
"benchmark": "yarn build && node scripts/benchmark.mjs",
|
|
55
61
|
"benchmark:corpus": "yarn build && node scripts/benchmark-corpus.mjs",
|
|
56
62
|
"benchmark:batch": "yarn build && node scripts/benchmark-batch.mjs",
|
|
57
63
|
"benchmark:formats": "yarn build && node scripts/benchmark-formats.mjs",
|
|
58
64
|
"benchmark:shapes": "yarn build && node scripts/benchmark-result-shapes.mjs",
|
|
59
65
|
"benchmark:agent-workflows": "yarn build && node scripts/benchmark-agent-workflows.mjs",
|
|
66
|
+
"benchmark:index-storage": "yarn build && node scripts/benchmark-index-storage.mjs",
|
|
67
|
+
"benchmark:index-integration": "yarn build && node scripts/benchmark-symbol-index-integration.mjs",
|
|
68
|
+
"benchmark:production-readiness": "yarn build && node scripts/canary-local-mcp.mjs run",
|
|
69
|
+
"benchmark:freeze-production-readiness": "node scripts/canary-local-mcp.mjs freeze-report-set",
|
|
60
70
|
"lint": "eslint src test scripts vitest.config.ts eslint.config.js",
|
|
61
71
|
"format": "prettier --write .",
|
|
62
72
|
"format:check": "prettier --check .",
|
|
@@ -82,5 +92,6 @@
|
|
|
82
92
|
"typescript": "^5.6.0",
|
|
83
93
|
"typescript-eslint": "^8.65.0",
|
|
84
94
|
"vitest": "^4.1.10"
|
|
85
|
-
}
|
|
95
|
+
},
|
|
96
|
+
"gitHead": "8dc92b344fa54b4e542b6222dace079c8ba2466b"
|
|
86
97
|
}
|