ast-mcp-server 0.5.1 → 0.7.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 +36 -0
- package/README.md +63 -14
- package/SECURITY.md +31 -0
- package/dist/cli.js +0 -0
- package/dist/index.js +129 -10
- package/dist/server.js +25 -2
- package/dist/services/agent-setup.js +36 -117
- package/dist/services/agent-targets.js +122 -0
- package/dist/services/context-builder.js +262 -0
- package/dist/services/diagnostics.js +10 -5
- package/dist/services/file-fingerprints.js +104 -0
- package/dist/services/file-snapshot.js +103 -0
- 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 +54 -0
- 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 +430 -47
- 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 +176 -0
- package/dist/tools/find_references.js +17 -55
- package/dist/tools/get_diagnostics.js +10 -7
- package/dist/tools/get_file.js +107 -0
- 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 -63
- package/docs/support.md +80 -0
- package/package.json +18 -6
- package/skills/structural-code-editing/SKILL.md +42 -8
- package/benchmark/README.md +0 -129
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DEFAULT_FILE_LINE_LIMIT, MAX_FILE_LINE_LIMIT, readFileSnapshot, } from "../services/file-snapshot.js";
|
|
3
|
+
import { buildFileOutline } from "../services/outline.js";
|
|
4
|
+
import { getSourceFileOrThrow, withProject } from "../services/project.js";
|
|
5
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
6
|
+
import { FRESHNESS_CAUSES, SNAPSHOT_STATES } from "../services/read-contracts.js";
|
|
7
|
+
import { createToolErrorContext, errorResult, structuredResult } from "./result.js";
|
|
8
|
+
const AstGetFileInputSchema = z.object({
|
|
9
|
+
project_root: z
|
|
10
|
+
.string()
|
|
11
|
+
.describe("Absolute project directory containing tsconfig.json, or the config path."),
|
|
12
|
+
file_path: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe("Project-relative or unambiguous source file path included by the active tsconfig."),
|
|
15
|
+
offset: z.number().int().min(0).default(0).describe("Zero-based line offset for source mode."),
|
|
16
|
+
limit: z
|
|
17
|
+
.number()
|
|
18
|
+
.int()
|
|
19
|
+
.min(1)
|
|
20
|
+
.max(MAX_FILE_LINE_LIMIT)
|
|
21
|
+
.default(DEFAULT_FILE_LINE_LIMIT)
|
|
22
|
+
.describe(`Maximum source lines to return, capped at ${MAX_FILE_LINE_LIMIT}.`),
|
|
23
|
+
symbols_only: z
|
|
24
|
+
.boolean()
|
|
25
|
+
.default(false)
|
|
26
|
+
.describe("Return body-free symbol selectors and signatures instead of source lines."),
|
|
27
|
+
});
|
|
28
|
+
const FileRangeSchema = z.object({
|
|
29
|
+
offset: z.number().int().min(0),
|
|
30
|
+
limit: z.number().int().min(1).max(MAX_FILE_LINE_LIMIT),
|
|
31
|
+
total_lines: z.number().int().min(0),
|
|
32
|
+
});
|
|
33
|
+
const FileLineSchema = z.object({
|
|
34
|
+
line: z.number().int().positive(),
|
|
35
|
+
text: z.string(),
|
|
36
|
+
});
|
|
37
|
+
const OutlineSymbolSchema = z.object({
|
|
38
|
+
symbolPath: z.string(),
|
|
39
|
+
name: z.string(),
|
|
40
|
+
kind: z.string(),
|
|
41
|
+
signature: z.string(),
|
|
42
|
+
startLine: z.number().int().positive(),
|
|
43
|
+
endLine: z.number().int().positive(),
|
|
44
|
+
});
|
|
45
|
+
const FileSnapshotBaseSchema = {
|
|
46
|
+
file: z.string(),
|
|
47
|
+
file_hash: z.string().regex(/^[0-9a-f]{64}$/),
|
|
48
|
+
snapshot_state: z.enum(["fresh", "stale"]),
|
|
49
|
+
freshness: z.object({
|
|
50
|
+
state: z.enum(SNAPSHOT_STATES),
|
|
51
|
+
causes: z.array(z.enum(FRESHNESS_CAUSES)),
|
|
52
|
+
checked_at: z.string().nullable(),
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
const AstGetFileOutputSchema = z.object({
|
|
56
|
+
mode: z.enum(["source", "symbols_only"]),
|
|
57
|
+
...FileSnapshotBaseSchema,
|
|
58
|
+
range: FileRangeSchema.optional(),
|
|
59
|
+
lines: z.array(FileLineSchema).optional(),
|
|
60
|
+
symbols: z.array(OutlineSymbolSchema).optional(),
|
|
61
|
+
});
|
|
62
|
+
export function registerGetFile(server) {
|
|
63
|
+
server.registerTool("ast_get_file", {
|
|
64
|
+
title: "Get bounded source file",
|
|
65
|
+
description: "Returns exact bounded source lines for one TypeScript/JavaScript file, or body-free symbols when symbols_only is true. Read-only; file snapshot freshness and project freshness are reported separately from compiler diagnostics.",
|
|
66
|
+
inputSchema: AstGetFileInputSchema,
|
|
67
|
+
outputSchema: AstGetFileOutputSchema,
|
|
68
|
+
annotations: {
|
|
69
|
+
readOnlyHint: true,
|
|
70
|
+
destructiveHint: false,
|
|
71
|
+
idempotentHint: true,
|
|
72
|
+
openWorldHint: false,
|
|
73
|
+
},
|
|
74
|
+
}, async ({ project_root, file_path, offset, limit, symbols_only }, extra) => {
|
|
75
|
+
const requestContext = createRequestContext(extra.signal);
|
|
76
|
+
try {
|
|
77
|
+
const structuredContent = await withProject(project_root, async ({ project, projectRoot, status }, operationContext) => {
|
|
78
|
+
const snapshot = await readFileSnapshot(project, projectRoot, file_path, { offset, limit }, operationContext);
|
|
79
|
+
const freshness = {
|
|
80
|
+
state: status.state,
|
|
81
|
+
causes: status.causes,
|
|
82
|
+
checked_at: status.lastSuccessfulSyncAt,
|
|
83
|
+
};
|
|
84
|
+
if (!symbols_only) {
|
|
85
|
+
return {
|
|
86
|
+
mode: "source",
|
|
87
|
+
...snapshot,
|
|
88
|
+
freshness,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const sourceFile = getSourceFileOrThrow(project, file_path);
|
|
92
|
+
return {
|
|
93
|
+
mode: "symbols_only",
|
|
94
|
+
file: snapshot.file,
|
|
95
|
+
file_hash: snapshot.file_hash,
|
|
96
|
+
snapshot_state: snapshot.snapshot_state,
|
|
97
|
+
freshness,
|
|
98
|
+
symbols: buildFileOutline(sourceFile, operationContext).symbols,
|
|
99
|
+
};
|
|
100
|
+
}, requestContext);
|
|
101
|
+
return structuredResult(structuredContent);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
return errorResult(error, createToolErrorContext("ast_get_file", project_root));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DEFAULT_IMPACT_MAX_DEPTH, DEFAULT_IMPACT_MAX_EDGES, DEFAULT_IMPACT_MAX_NODES, IMPACT_DIRECTIONS, MAX_IMPACT_DEPTH, MAX_IMPACT_EDGES, MAX_IMPACT_NODES, resolveImpactRoot, traverseCompilerImpact, } from "../services/impact.js";
|
|
3
|
+
import { FRESHNESS_CAUSES, SNAPSHOT_STATES, TRUNCATION_REASONS, } from "../services/read-contracts.js";
|
|
4
|
+
import { withProject } from "../services/project.js";
|
|
5
|
+
import { createRequestContext } from "../services/request-context.js";
|
|
6
|
+
import { RELATIONSHIP_CONFIDENCES, RELATIONSHIP_EDGE_KINDS, RELATIONSHIP_PROVENANCES, RELATIONSHIP_RESOLUTIONS, } from "../services/relationships.js";
|
|
7
|
+
import { createToolErrorContext, errorResult, formattedResult, ToolOutputFormatInputSchema, } from "./result.js";
|
|
8
|
+
const AstGetImpactInputSchema = z.object({
|
|
9
|
+
project_root: z
|
|
10
|
+
.string()
|
|
11
|
+
.describe("Absolute project directory containing tsconfig.json, or the config path."),
|
|
12
|
+
file_path: z.string().describe("Project-relative file containing the exact impact root."),
|
|
13
|
+
symbol_path: z.string().describe("Exact symbol path returned by an outline or symbol search."),
|
|
14
|
+
direction: z
|
|
15
|
+
.enum(IMPACT_DIRECTIONS)
|
|
16
|
+
.default("both")
|
|
17
|
+
.describe("Traversal direction relative to the root symbol."),
|
|
18
|
+
max_depth: z
|
|
19
|
+
.number()
|
|
20
|
+
.int()
|
|
21
|
+
.min(0)
|
|
22
|
+
.max(MAX_IMPACT_DEPTH)
|
|
23
|
+
.default(DEFAULT_IMPACT_MAX_DEPTH)
|
|
24
|
+
.describe("Maximum relationship depth, including zero for the root only."),
|
|
25
|
+
max_nodes: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(MAX_IMPACT_NODES)
|
|
30
|
+
.default(DEFAULT_IMPACT_MAX_NODES)
|
|
31
|
+
.describe("Maximum returned impact nodes, including the root."),
|
|
32
|
+
max_edges: z
|
|
33
|
+
.number()
|
|
34
|
+
.int()
|
|
35
|
+
.min(1)
|
|
36
|
+
.max(MAX_IMPACT_EDGES)
|
|
37
|
+
.default(DEFAULT_IMPACT_MAX_EDGES)
|
|
38
|
+
.describe("Maximum returned relationship edges."),
|
|
39
|
+
relationship_kinds: z
|
|
40
|
+
.array(z.enum(RELATIONSHIP_EDGE_KINDS))
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Optional relationship kinds to traverse; defaults to all normalized kinds."),
|
|
43
|
+
...ToolOutputFormatInputSchema,
|
|
44
|
+
});
|
|
45
|
+
const SourceRangeSchema = z.object({
|
|
46
|
+
start_line: z.number().int().positive(),
|
|
47
|
+
start_column: z.number().int().positive().optional(),
|
|
48
|
+
end_line: z.number().int().positive().optional(),
|
|
49
|
+
end_column: z.number().int().positive().optional(),
|
|
50
|
+
});
|
|
51
|
+
const RelationshipEndpointSchema = z.object({
|
|
52
|
+
file: z.string(),
|
|
53
|
+
symbol_path: z.string(),
|
|
54
|
+
selector: z.string(),
|
|
55
|
+
range: SourceRangeSchema.optional(),
|
|
56
|
+
});
|
|
57
|
+
const FreshnessSchema = z.object({
|
|
58
|
+
state: z.enum(SNAPSHOT_STATES),
|
|
59
|
+
causes: z.array(z.enum(FRESHNESS_CAUSES)),
|
|
60
|
+
checked_at: z.string().nullable(),
|
|
61
|
+
});
|
|
62
|
+
const RelationshipEdgeSchema = z.object({
|
|
63
|
+
relationship_id: z.string(),
|
|
64
|
+
source: RelationshipEndpointSchema,
|
|
65
|
+
target: RelationshipEndpointSchema,
|
|
66
|
+
kind: z.enum(RELATIONSHIP_EDGE_KINDS),
|
|
67
|
+
provenance: z.enum(RELATIONSHIP_PROVENANCES),
|
|
68
|
+
confidence: z.enum(RELATIONSHIP_CONFIDENCES),
|
|
69
|
+
resolution: z.enum(RELATIONSHIP_RESOLUTIONS),
|
|
70
|
+
compiler_authoritative: z.boolean(),
|
|
71
|
+
freshness: FreshnessSchema,
|
|
72
|
+
});
|
|
73
|
+
const ImpactOutputSchema = z.object({
|
|
74
|
+
root: RelationshipEndpointSchema,
|
|
75
|
+
direction: z.enum(IMPACT_DIRECTIONS),
|
|
76
|
+
relationship_kinds: z.array(z.enum(RELATIONSHIP_EDGE_KINDS)),
|
|
77
|
+
nodes: z.array(z.object({
|
|
78
|
+
endpoint: RelationshipEndpointSchema,
|
|
79
|
+
depth: z.number().int().min(0),
|
|
80
|
+
direct: z.boolean(),
|
|
81
|
+
})),
|
|
82
|
+
edges: z.array(RelationshipEdgeSchema),
|
|
83
|
+
visited_nodes: z.number().int().min(0),
|
|
84
|
+
visited_edges: z.number().int().min(0),
|
|
85
|
+
max_depth_reached: z.number().int().min(0),
|
|
86
|
+
max_depth: z.number().int().min(0),
|
|
87
|
+
max_nodes: z.number().int().positive(),
|
|
88
|
+
max_edges: z.number().int().positive(),
|
|
89
|
+
incomplete: z.boolean(),
|
|
90
|
+
truncation: z.object({
|
|
91
|
+
truncated: z.boolean(),
|
|
92
|
+
reason: z.enum(TRUNCATION_REASONS).nullable(),
|
|
93
|
+
}),
|
|
94
|
+
truncation_reasons: z.array(z.enum(TRUNCATION_REASONS)),
|
|
95
|
+
freshness: FreshnessSchema,
|
|
96
|
+
});
|
|
97
|
+
export function registerGetImpact(server) {
|
|
98
|
+
server.registerTool("ast_get_impact", {
|
|
99
|
+
title: "Get bounded compiler-backed impact",
|
|
100
|
+
description: "Traverses exact compiler-backed relationships around a symbol with explicit direction, depth, node and edge budgets. Returns bounded session freshness metadata and fails closed when exact relationships are not fresh. Read-only evidence only; it never prepares or applies mutations.",
|
|
101
|
+
inputSchema: AstGetImpactInputSchema,
|
|
102
|
+
outputSchema: ImpactOutputSchema,
|
|
103
|
+
annotations: {
|
|
104
|
+
readOnlyHint: true,
|
|
105
|
+
destructiveHint: false,
|
|
106
|
+
idempotentHint: true,
|
|
107
|
+
openWorldHint: false,
|
|
108
|
+
},
|
|
109
|
+
}, async ({ project_root, file_path, symbol_path, direction, max_depth, max_nodes, max_edges, relationship_kinds, output_format, }, extra) => {
|
|
110
|
+
const requestContext = createRequestContext(extra.signal);
|
|
111
|
+
try {
|
|
112
|
+
const structuredContent = await withProject(project_root, (context, operationContext) => {
|
|
113
|
+
if (context.status.state !== "fresh") {
|
|
114
|
+
throw new Error("Compiler-backed impact evidence is not fresh. Retry the read.");
|
|
115
|
+
}
|
|
116
|
+
const freshness = {
|
|
117
|
+
state: context.status.state,
|
|
118
|
+
causes: context.status.causes,
|
|
119
|
+
checked_at: context.status.lastSuccessfulSyncAt,
|
|
120
|
+
};
|
|
121
|
+
const root = resolveImpactRoot(context.project, context.projectRoot, { file_path, symbol_path }, operationContext);
|
|
122
|
+
const impact = traverseCompilerImpact(context.project, context.projectRoot, root, freshness, { direction, max_depth, max_nodes, max_edges, relationship_kinds }, operationContext);
|
|
123
|
+
return {
|
|
124
|
+
...impact,
|
|
125
|
+
freshness,
|
|
126
|
+
};
|
|
127
|
+
}, requestContext);
|
|
128
|
+
return formattedResult(ImpactOutputSchema, structuredContent, output_format);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
return errorResult(error, createToolErrorContext("ast_get_impact", project_root));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
@@ -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,
|