@aiready/ast-mcp-server 0.1.0 → 0.1.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/dist/index.cjs +771 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +742 -0
- package/dist/index.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import {
|
|
5
|
+
CallToolRequestSchema,
|
|
6
|
+
ListToolsRequestSchema
|
|
7
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
|
|
9
|
+
// src/schemas.ts
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
var ResolveDefinitionSchema = z.object({
|
|
12
|
+
symbol: z.string().describe(
|
|
13
|
+
"Symbol name to resolve (function, class, type, interface, etc.)"
|
|
14
|
+
),
|
|
15
|
+
path: z.string().describe("Project root or target directory")
|
|
16
|
+
});
|
|
17
|
+
var FindReferencesSchema = z.object({
|
|
18
|
+
symbol: z.string().describe("Symbol name to find references for"),
|
|
19
|
+
path: z.string().describe("Project root directory"),
|
|
20
|
+
limit: z.number().optional().default(50).describe("Max results per page (default 50)"),
|
|
21
|
+
offset: z.number().optional().default(0).describe("Pagination offset")
|
|
22
|
+
});
|
|
23
|
+
var FindImplementationsSchema = z.object({
|
|
24
|
+
symbol: z.string().describe("Interface or abstract class name to find implementations for"),
|
|
25
|
+
path: z.string().describe("Project root directory"),
|
|
26
|
+
limit: z.number().optional().default(50).describe("Max results per page"),
|
|
27
|
+
offset: z.number().optional().default(0).describe("Pagination offset")
|
|
28
|
+
});
|
|
29
|
+
var GetFileStructureSchema = z.object({
|
|
30
|
+
file: z.string().describe("Absolute path to the file to analyze")
|
|
31
|
+
});
|
|
32
|
+
var SearchCodeSchema = z.object({
|
|
33
|
+
pattern: z.string().describe("Search pattern (regex)"),
|
|
34
|
+
path: z.string().describe("Directory to search in"),
|
|
35
|
+
filePattern: z.string().optional().describe('Glob filter (e.g., "*.ts")'),
|
|
36
|
+
limit: z.number().optional().default(50).describe("Max matches to return")
|
|
37
|
+
});
|
|
38
|
+
var GetSymbolDocsSchema = z.object({
|
|
39
|
+
symbol: z.string().describe("Symbol name to get documentation for"),
|
|
40
|
+
path: z.string().describe("Project root directory")
|
|
41
|
+
});
|
|
42
|
+
var BuildSymbolIndexSchema = z.object({
|
|
43
|
+
path: z.string().describe("Project root directory to index")
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// src/adapters/typescript-adapter.ts
|
|
47
|
+
import {
|
|
48
|
+
Node,
|
|
49
|
+
SyntaxKind
|
|
50
|
+
} from "ts-morph";
|
|
51
|
+
|
|
52
|
+
// src/project-manager.ts
|
|
53
|
+
import { Project } from "ts-morph";
|
|
54
|
+
import path from "path";
|
|
55
|
+
import fs from "fs";
|
|
56
|
+
import { glob } from "glob";
|
|
57
|
+
var ProjectManager = class {
|
|
58
|
+
projects = /* @__PURE__ */ new Map();
|
|
59
|
+
tsconfigCache = /* @__PURE__ */ new Map();
|
|
60
|
+
/**
|
|
61
|
+
* Find all tsconfig.json files in a directory (recursive)
|
|
62
|
+
*/
|
|
63
|
+
async findTsConfigs(rootDir) {
|
|
64
|
+
if (this.tsconfigCache.has(rootDir)) {
|
|
65
|
+
return this.tsconfigCache.get(rootDir);
|
|
66
|
+
}
|
|
67
|
+
const configs = await glob("**/tsconfig.json", {
|
|
68
|
+
cwd: rootDir,
|
|
69
|
+
absolute: true,
|
|
70
|
+
ignore: ["**/node_modules/**", "**/dist/**"]
|
|
71
|
+
});
|
|
72
|
+
this.tsconfigCache.set(rootDir, configs);
|
|
73
|
+
return configs;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get or create a Project for a specific file path
|
|
77
|
+
*/
|
|
78
|
+
async getProjectForFile(filePath) {
|
|
79
|
+
const tsconfigPath = await this.findNearestTsConfig(filePath);
|
|
80
|
+
if (!tsconfigPath) return void 0;
|
|
81
|
+
return this.getOrCreateProject(tsconfigPath);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get or create a Project for a tsconfig path
|
|
85
|
+
*/
|
|
86
|
+
getOrCreateProject(tsconfigPath) {
|
|
87
|
+
if (this.projects.has(tsconfigPath)) {
|
|
88
|
+
return this.projects.get(tsconfigPath);
|
|
89
|
+
}
|
|
90
|
+
const project = new Project({
|
|
91
|
+
tsConfigFilePath: tsconfigPath,
|
|
92
|
+
skipAddingFilesFromTsConfig: false
|
|
93
|
+
});
|
|
94
|
+
this.projects.set(tsconfigPath, project);
|
|
95
|
+
return project;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Find the nearest tsconfig.json for a file
|
|
99
|
+
*/
|
|
100
|
+
async findNearestTsConfig(filePath) {
|
|
101
|
+
let currentDir = path.dirname(filePath);
|
|
102
|
+
const root = path.parse(currentDir).root;
|
|
103
|
+
while (currentDir !== root) {
|
|
104
|
+
const tsconfigPath = path.join(currentDir, "tsconfig.json");
|
|
105
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
106
|
+
return tsconfigPath;
|
|
107
|
+
}
|
|
108
|
+
currentDir = path.dirname(currentDir);
|
|
109
|
+
}
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get all projects that might contain a file or serve a path
|
|
114
|
+
*/
|
|
115
|
+
async getProjectsForPath(rootDir) {
|
|
116
|
+
const configs = await this.findTsConfigs(rootDir);
|
|
117
|
+
return configs.map((config) => this.getOrCreateProject(config));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Dispose all projects to free memory
|
|
121
|
+
*/
|
|
122
|
+
disposeAll() {
|
|
123
|
+
for (const project of this.projects.values()) {
|
|
124
|
+
project.getLanguageService().compilerObject.dispose();
|
|
125
|
+
}
|
|
126
|
+
this.projects.clear();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var projectManager = new ProjectManager();
|
|
130
|
+
|
|
131
|
+
// src/adapters/typescript-adapter.ts
|
|
132
|
+
var TypeScriptAdapter = class {
|
|
133
|
+
/**
|
|
134
|
+
* Resolve definition of a symbol at a path
|
|
135
|
+
*/
|
|
136
|
+
async resolveDefinition(symbolName, path2) {
|
|
137
|
+
const projects = await projectManager.getProjectsForPath(path2);
|
|
138
|
+
const results = [];
|
|
139
|
+
for (const project of projects) {
|
|
140
|
+
const sourceFiles = project.getSourceFiles();
|
|
141
|
+
for (const sourceFile of sourceFiles) {
|
|
142
|
+
const nodes = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier).filter((id) => id.getText() === symbolName);
|
|
143
|
+
for (const node of nodes) {
|
|
144
|
+
const definitions = node.getDefinitionNodes();
|
|
145
|
+
for (const defNode of definitions) {
|
|
146
|
+
results.push(this.mapToDefinitionLocation(defNode));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return this.deduplicateLocations(results);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Find references to a symbol
|
|
155
|
+
*/
|
|
156
|
+
async findReferences(symbolName, path2, limit = 50, offset = 0) {
|
|
157
|
+
const projects = await projectManager.getProjectsForPath(path2);
|
|
158
|
+
const results = [];
|
|
159
|
+
for (const project of projects) {
|
|
160
|
+
const sourceFiles = project.getSourceFiles();
|
|
161
|
+
for (const sourceFile of sourceFiles) {
|
|
162
|
+
const nodes = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier).filter((id) => id.getText() === symbolName);
|
|
163
|
+
for (const node of nodes) {
|
|
164
|
+
const referencedSymbols = node.findReferences();
|
|
165
|
+
for (const referencedSymbol of referencedSymbols) {
|
|
166
|
+
const references = referencedSymbol.getReferences();
|
|
167
|
+
for (const ref of references) {
|
|
168
|
+
const sourceFile2 = ref.getSourceFile();
|
|
169
|
+
const lineAndColumn = sourceFile2.getLineAndColumnAtPos(
|
|
170
|
+
ref.getTextSpan().getStart()
|
|
171
|
+
);
|
|
172
|
+
results.push({
|
|
173
|
+
file: sourceFile2.getFilePath(),
|
|
174
|
+
line: lineAndColumn.line,
|
|
175
|
+
column: lineAndColumn.column,
|
|
176
|
+
text: ref.getNode().getParent()?.getText() || ref.getNode().getText()
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const uniqueResults = this.deduplicateLocations(results);
|
|
184
|
+
return {
|
|
185
|
+
references: uniqueResults.slice(offset, offset + limit),
|
|
186
|
+
total_count: uniqueResults.length
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Find implementations for a symbol
|
|
191
|
+
*/
|
|
192
|
+
async findImplementations(symbolName, path2, limit = 50, offset = 0) {
|
|
193
|
+
const projects = await projectManager.getProjectsForPath(path2);
|
|
194
|
+
const results = [];
|
|
195
|
+
for (const project of projects) {
|
|
196
|
+
const sourceFiles = project.getSourceFiles();
|
|
197
|
+
for (const sourceFile of sourceFiles) {
|
|
198
|
+
const nodes = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier).filter((id) => id.getText() === symbolName);
|
|
199
|
+
for (const node of nodes) {
|
|
200
|
+
const implementations = node.getImplementations();
|
|
201
|
+
for (const impl of implementations) {
|
|
202
|
+
const sourceFile2 = impl.getSourceFile();
|
|
203
|
+
const lineAndColumn = sourceFile2.getLineAndColumnAtPos(
|
|
204
|
+
impl.getTextSpan().getStart()
|
|
205
|
+
);
|
|
206
|
+
results.push({
|
|
207
|
+
file: sourceFile2.getFilePath(),
|
|
208
|
+
line: lineAndColumn.line,
|
|
209
|
+
column: lineAndColumn.column,
|
|
210
|
+
text: impl.getNode().getParent()?.getText() || impl.getNode().getText()
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const uniqueResults = this.deduplicateLocations(results);
|
|
217
|
+
return {
|
|
218
|
+
implementations: uniqueResults.slice(offset, offset + limit),
|
|
219
|
+
total_count: uniqueResults.length
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get file structure overview
|
|
224
|
+
*/
|
|
225
|
+
async getFileStructure(filePath) {
|
|
226
|
+
const project = await projectManager.getProjectForFile(filePath);
|
|
227
|
+
if (!project) return void 0;
|
|
228
|
+
const sourceFile = project.getSourceFile(filePath);
|
|
229
|
+
if (!sourceFile) return void 0;
|
|
230
|
+
const structure = {
|
|
231
|
+
file: filePath,
|
|
232
|
+
imports: sourceFile.getImportDeclarations().map((imp) => ({
|
|
233
|
+
module: imp.getModuleSpecifierValue(),
|
|
234
|
+
names: imp.getNamedImports().map((ni) => ni.getName())
|
|
235
|
+
})),
|
|
236
|
+
exports: sourceFile.getExportSymbols().map((sym) => ({
|
|
237
|
+
name: sym.getName(),
|
|
238
|
+
kind: this.mapSymbolKind(sym)
|
|
239
|
+
})),
|
|
240
|
+
classes: sourceFile.getClasses().map((cls) => this.mapToClassInfo(cls)),
|
|
241
|
+
functions: sourceFile.getFunctions().map((fn) => this.mapToFunctionInfo(fn)),
|
|
242
|
+
interfaces: sourceFile.getInterfaces().map((itf) => this.mapToInterfaceInfo(itf)),
|
|
243
|
+
typeAliases: sourceFile.getTypeAliases().map((ta) => this.mapToTypeAliasInfo(ta)),
|
|
244
|
+
enums: sourceFile.getEnums().map((enm) => this.mapToEnumInfo(enm))
|
|
245
|
+
};
|
|
246
|
+
return structure;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Helper: Map ts-morph Node (Declaration) to DefinitionLocation
|
|
250
|
+
*/
|
|
251
|
+
mapToDefinitionLocation(node) {
|
|
252
|
+
const sourceFile = node.getSourceFile();
|
|
253
|
+
const lineAndColumn = sourceFile.getLineAndColumnAtPos(node.getStart());
|
|
254
|
+
return {
|
|
255
|
+
file: sourceFile.getFilePath(),
|
|
256
|
+
line: lineAndColumn.line,
|
|
257
|
+
column: lineAndColumn.column,
|
|
258
|
+
kind: this.mapNodeToSymbolKind(node),
|
|
259
|
+
snippet: node.getText(),
|
|
260
|
+
documentation: this.getJsDoc(node)
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Helper: Map ts-morph Node to SymbolKind
|
|
265
|
+
*/
|
|
266
|
+
mapNodeToSymbolKind(node) {
|
|
267
|
+
if (Node.isClassDeclaration(node)) return "class";
|
|
268
|
+
if (Node.isFunctionDeclaration(node)) return "function";
|
|
269
|
+
if (Node.isInterfaceDeclaration(node)) return "interface";
|
|
270
|
+
if (Node.isTypeAliasDeclaration(node)) return "type_alias";
|
|
271
|
+
if (Node.isEnumDeclaration(node)) return "enum";
|
|
272
|
+
if (Node.isVariableDeclaration(node)) return "variable";
|
|
273
|
+
if (Node.isMethodDeclaration(node)) return "method";
|
|
274
|
+
if (Node.isPropertyDeclaration(node)) return "property";
|
|
275
|
+
if (Node.isParameterDeclaration(node)) return "parameter";
|
|
276
|
+
return "variable";
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Helper: Map Symbol to SymbolKind
|
|
280
|
+
*/
|
|
281
|
+
mapSymbolKind(symbol) {
|
|
282
|
+
const decls = symbol.getDeclarations();
|
|
283
|
+
if (decls.length > 0) return this.mapNodeToSymbolKind(decls[0]);
|
|
284
|
+
return "variable";
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Helper: Get JSDoc from Node
|
|
288
|
+
*/
|
|
289
|
+
getJsDoc(node) {
|
|
290
|
+
if (Node.isJSDocable(node)) {
|
|
291
|
+
const docs = node.getJsDocs();
|
|
292
|
+
if (docs.length > 0) {
|
|
293
|
+
return docs[0].getCommentText();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return void 0;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Helper: Get full JSDoc info (with tags)
|
|
300
|
+
*/
|
|
301
|
+
getSymbolDocs(node) {
|
|
302
|
+
if (Node.isJSDocable(node)) {
|
|
303
|
+
const docs = node.getJsDocs();
|
|
304
|
+
if (docs.length > 0) {
|
|
305
|
+
const doc = docs[0];
|
|
306
|
+
return {
|
|
307
|
+
documentation: doc.getCommentText(),
|
|
308
|
+
tags: doc.getTags().map((tag) => ({
|
|
309
|
+
name: tag.getTagName(),
|
|
310
|
+
text: tag.getCommentText() || ""
|
|
311
|
+
}))
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return void 0;
|
|
316
|
+
}
|
|
317
|
+
mapToClassInfo(cls) {
|
|
318
|
+
return {
|
|
319
|
+
name: cls.getName() || "anonymous",
|
|
320
|
+
...this.getSymbolDocs(cls),
|
|
321
|
+
methods: cls.getMethods().map((m) => this.mapToFunctionInfo(m)),
|
|
322
|
+
properties: cls.getProperties().map((p) => this.mapToPropertyInfo(p))
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
mapToFunctionInfo(fn) {
|
|
326
|
+
return {
|
|
327
|
+
name: fn.getName() || "anonymous",
|
|
328
|
+
...this.getSymbolDocs(fn),
|
|
329
|
+
params: fn.getParameters().map((p) => ({
|
|
330
|
+
name: p.getName(),
|
|
331
|
+
type: p.getType().getText()
|
|
332
|
+
})),
|
|
333
|
+
returnType: fn.getReturnType().getText()
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
mapToPropertyInfo(p) {
|
|
337
|
+
return {
|
|
338
|
+
name: p.getName(),
|
|
339
|
+
type: p.getType().getText(),
|
|
340
|
+
...this.getSymbolDocs(p)
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
mapToInterfaceInfo(itf) {
|
|
344
|
+
return {
|
|
345
|
+
name: itf.getName(),
|
|
346
|
+
...this.getSymbolDocs(itf),
|
|
347
|
+
properties: itf.getProperties().map((p) => this.mapToPropertyInfo(p)),
|
|
348
|
+
methods: itf.getMethods().map((m) => this.mapToFunctionInfo(m))
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
mapToTypeAliasInfo(ta) {
|
|
352
|
+
return {
|
|
353
|
+
name: ta.getName(),
|
|
354
|
+
type: ta.getType().getText(),
|
|
355
|
+
...this.getSymbolDocs(ta)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
mapToEnumInfo(enm) {
|
|
359
|
+
return {
|
|
360
|
+
name: enm.getName(),
|
|
361
|
+
...this.getSymbolDocs(enm),
|
|
362
|
+
members: enm.getMembers().map((m) => m.getName())
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Helper: Deduplicate locations
|
|
367
|
+
*/
|
|
368
|
+
deduplicateLocations(locations) {
|
|
369
|
+
const seen = /* @__PURE__ */ new Set();
|
|
370
|
+
return locations.filter((loc) => {
|
|
371
|
+
const key = `${loc.file}:${loc.line}:${loc.column}`;
|
|
372
|
+
if (seen.has(key)) return false;
|
|
373
|
+
seen.add(key);
|
|
374
|
+
return true;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
var typescriptAdapter = new TypeScriptAdapter();
|
|
379
|
+
|
|
380
|
+
// src/tools/resolve-definition.ts
|
|
381
|
+
async function resolveDefinition(symbol, path2) {
|
|
382
|
+
return await typescriptAdapter.resolveDefinition(symbol, path2);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/tools/find-references.ts
|
|
386
|
+
async function findReferences(symbol, path2, limit = 50, offset = 0) {
|
|
387
|
+
return await typescriptAdapter.findReferences(symbol, path2, limit, offset);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/tools/find-implementations.ts
|
|
391
|
+
async function findImplementations(symbol, path2, limit = 50, offset = 0) {
|
|
392
|
+
return await typescriptAdapter.findImplementations(
|
|
393
|
+
symbol,
|
|
394
|
+
path2,
|
|
395
|
+
limit,
|
|
396
|
+
offset
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/tools/get-file-structure.ts
|
|
401
|
+
async function getFileStructure(file) {
|
|
402
|
+
return await typescriptAdapter.getFileStructure(file);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/tools/search-code.ts
|
|
406
|
+
import { execFile } from "child_process";
|
|
407
|
+
import { promisify } from "util";
|
|
408
|
+
import { rgPath } from "@vscode/ripgrep";
|
|
409
|
+
var execFileAsync = promisify(execFile);
|
|
410
|
+
async function searchCode(pattern, searchPath, filePattern, limit = 50) {
|
|
411
|
+
const args = [
|
|
412
|
+
"--json",
|
|
413
|
+
"--max-count",
|
|
414
|
+
limit.toString(),
|
|
415
|
+
"--fixed-strings",
|
|
416
|
+
// Default to fixed strings unless we want regex
|
|
417
|
+
pattern,
|
|
418
|
+
searchPath
|
|
419
|
+
];
|
|
420
|
+
if (filePattern) {
|
|
421
|
+
args.push("--glob", filePattern);
|
|
422
|
+
}
|
|
423
|
+
args.push("--glob", "!**/node_modules/**");
|
|
424
|
+
args.push("--glob", "!**/dist/**");
|
|
425
|
+
args.push("--glob", "!**/.git/**");
|
|
426
|
+
try {
|
|
427
|
+
const { stdout } = await execFileAsync(rgPath, args);
|
|
428
|
+
const lines = stdout.split("\n").filter(Boolean);
|
|
429
|
+
const results = [];
|
|
430
|
+
for (const line of lines) {
|
|
431
|
+
const data = JSON.parse(line);
|
|
432
|
+
if (data.type === "match") {
|
|
433
|
+
const file = data.data.path.text;
|
|
434
|
+
const lineNumber = data.data.line_number;
|
|
435
|
+
const submatches = data.data.submatches;
|
|
436
|
+
for (const submatch of submatches) {
|
|
437
|
+
results.push({
|
|
438
|
+
file,
|
|
439
|
+
line: lineNumber,
|
|
440
|
+
column: submatch.start,
|
|
441
|
+
text: data.data.lines.text.trim()
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return results.slice(0, limit);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (error.code === 1) {
|
|
449
|
+
return [];
|
|
450
|
+
}
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/tools/get-symbol-docs.ts
|
|
456
|
+
import { SyntaxKind as SyntaxKind2 } from "ts-morph";
|
|
457
|
+
async function getSymbolDocs(symbol, filePath) {
|
|
458
|
+
const projects = await projectManager.getProjectsForPath(filePath);
|
|
459
|
+
for (const project of projects) {
|
|
460
|
+
const sourceFile = project.getSourceFile(filePath);
|
|
461
|
+
if (sourceFile) {
|
|
462
|
+
const node = sourceFile.getDescendantsOfKind(SyntaxKind2.Identifier).find((id) => id.getText() === symbol);
|
|
463
|
+
if (node) {
|
|
464
|
+
const decls = node.getSymbol()?.getDeclarations();
|
|
465
|
+
if (decls && decls.length > 0) {
|
|
466
|
+
const docs = typescriptAdapter.getSymbolDocs(decls[0]);
|
|
467
|
+
if (docs) {
|
|
468
|
+
return {
|
|
469
|
+
symbol,
|
|
470
|
+
file: sourceFile.getFilePath(),
|
|
471
|
+
line: sourceFile.getLineAndColumnAtPos(decls[0].getStart()).line,
|
|
472
|
+
...docs
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return void 0;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/index/symbol-index.ts
|
|
483
|
+
var SymbolIndex = class {
|
|
484
|
+
/**
|
|
485
|
+
* Build/Warm the index for a given path
|
|
486
|
+
*/
|
|
487
|
+
async buildIndex(rootDir) {
|
|
488
|
+
const startTime = Date.now();
|
|
489
|
+
const projects = await projectManager.getProjectsForPath(rootDir);
|
|
490
|
+
let fileCount = 0;
|
|
491
|
+
let functionCount = 0;
|
|
492
|
+
let classCount = 0;
|
|
493
|
+
let interfaceCount = 0;
|
|
494
|
+
let typeCount = 0;
|
|
495
|
+
for (const project of projects) {
|
|
496
|
+
const sourceFiles = project.getSourceFiles();
|
|
497
|
+
fileCount += sourceFiles.length;
|
|
498
|
+
for (const sourceFile of sourceFiles) {
|
|
499
|
+
functionCount += sourceFile.getFunctions().length;
|
|
500
|
+
classCount += sourceFile.getClasses().length;
|
|
501
|
+
interfaceCount += sourceFile.getInterfaces().length;
|
|
502
|
+
typeCount += sourceFile.getTypeAliases().length;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const duration = Date.now() - startTime;
|
|
506
|
+
const memoryUsage = process.memoryUsage().heapUsed / 1024 / 1024;
|
|
507
|
+
return {
|
|
508
|
+
indexed: {
|
|
509
|
+
files: fileCount,
|
|
510
|
+
functions: functionCount,
|
|
511
|
+
classes: classCount,
|
|
512
|
+
interfaces: interfaceCount,
|
|
513
|
+
types: typeCount
|
|
514
|
+
},
|
|
515
|
+
duration_ms: duration,
|
|
516
|
+
memory_mb: Math.round(memoryUsage)
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
var symbolIndex = new SymbolIndex();
|
|
521
|
+
|
|
522
|
+
// src/tools/build-symbol-index.ts
|
|
523
|
+
async function buildSymbolIndex(path2) {
|
|
524
|
+
return await symbolIndex.buildIndex(path2);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/index.ts
|
|
528
|
+
var ASTExplorerServer = class {
|
|
529
|
+
server;
|
|
530
|
+
version = "0.1.0";
|
|
531
|
+
constructor() {
|
|
532
|
+
this.server = new Server(
|
|
533
|
+
{
|
|
534
|
+
name: "ast-explorer-server",
|
|
535
|
+
version: this.version
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
capabilities: {
|
|
539
|
+
tools: {}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
this.setupHandlers();
|
|
544
|
+
this.server.onerror = (error) => {
|
|
545
|
+
console.error("[MCP Error]", error);
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
setupHandlers() {
|
|
549
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
550
|
+
return {
|
|
551
|
+
tools: [
|
|
552
|
+
{
|
|
553
|
+
name: "resolve_definition",
|
|
554
|
+
description: "Find where a symbol is defined using TypeScript AST.",
|
|
555
|
+
inputSchema: {
|
|
556
|
+
type: "object",
|
|
557
|
+
properties: {
|
|
558
|
+
symbol: {
|
|
559
|
+
type: "string",
|
|
560
|
+
description: "Symbol name (e.g., function, class)"
|
|
561
|
+
},
|
|
562
|
+
path: {
|
|
563
|
+
type: "string",
|
|
564
|
+
description: "Project root or target directory"
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
required: ["symbol", "path"]
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
name: "find_references",
|
|
572
|
+
description: "Find all usages of a symbol across the project.",
|
|
573
|
+
inputSchema: {
|
|
574
|
+
type: "object",
|
|
575
|
+
properties: {
|
|
576
|
+
symbol: { type: "string", description: "Symbol name" },
|
|
577
|
+
path: { type: "string", description: "Project root" },
|
|
578
|
+
limit: { type: "number", default: 50 },
|
|
579
|
+
offset: { type: "number", default: 0 }
|
|
580
|
+
},
|
|
581
|
+
required: ["symbol", "path"]
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "find_implementations",
|
|
586
|
+
description: "Find implementations of interfaces or abstract classes.",
|
|
587
|
+
inputSchema: {
|
|
588
|
+
type: "object",
|
|
589
|
+
properties: {
|
|
590
|
+
symbol: { type: "string", description: "Interface/Class name" },
|
|
591
|
+
path: { type: "string", description: "Project root" },
|
|
592
|
+
limit: { type: "number", default: 50 },
|
|
593
|
+
offset: { type: "number", default: 0 }
|
|
594
|
+
},
|
|
595
|
+
required: ["symbol", "path"]
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
name: "get_file_structure",
|
|
600
|
+
description: "Get structural overview of a file (imports, exports, symbols).",
|
|
601
|
+
inputSchema: {
|
|
602
|
+
type: "object",
|
|
603
|
+
properties: {
|
|
604
|
+
file: { type: "string", description: "Absolute path to file" }
|
|
605
|
+
},
|
|
606
|
+
required: ["file"]
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
name: "search_code",
|
|
611
|
+
description: "Fast regex search via bundled ripgrep.",
|
|
612
|
+
inputSchema: {
|
|
613
|
+
type: "object",
|
|
614
|
+
properties: {
|
|
615
|
+
pattern: { type: "string", description: "Search pattern" },
|
|
616
|
+
path: { type: "string", description: "Directory to search" },
|
|
617
|
+
filePattern: { type: "string", description: "Glob filter" },
|
|
618
|
+
limit: { type: "number", default: 50 }
|
|
619
|
+
},
|
|
620
|
+
required: ["pattern", "path"]
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
name: "get_symbol_docs",
|
|
625
|
+
description: "Get JSDoc/TSDoc for a specific symbol.",
|
|
626
|
+
inputSchema: {
|
|
627
|
+
type: "object",
|
|
628
|
+
properties: {
|
|
629
|
+
symbol: { type: "string", description: "Symbol name" },
|
|
630
|
+
path: { type: "string", description: "Project root" }
|
|
631
|
+
},
|
|
632
|
+
required: ["symbol", "path"]
|
|
633
|
+
}
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
name: "build_symbol_index",
|
|
637
|
+
description: "Warm the symbol index for faster navigation.",
|
|
638
|
+
inputSchema: {
|
|
639
|
+
type: "object",
|
|
640
|
+
properties: {
|
|
641
|
+
path: { type: "string", description: "Project root to index" }
|
|
642
|
+
},
|
|
643
|
+
required: ["path"]
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
]
|
|
647
|
+
};
|
|
648
|
+
});
|
|
649
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
650
|
+
const { name, arguments: args } = request.params;
|
|
651
|
+
try {
|
|
652
|
+
switch (name) {
|
|
653
|
+
case "resolve_definition": {
|
|
654
|
+
const { symbol, path: path2 } = ResolveDefinitionSchema.parse(args);
|
|
655
|
+
const results = await resolveDefinition(symbol, path2);
|
|
656
|
+
return {
|
|
657
|
+
content: [
|
|
658
|
+
{ type: "text", text: JSON.stringify(results, null, 2) }
|
|
659
|
+
]
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
case "find_references": {
|
|
663
|
+
const { symbol, path: path2, limit, offset } = FindReferencesSchema.parse(args);
|
|
664
|
+
const results = await findReferences(symbol, path2, limit, offset);
|
|
665
|
+
return {
|
|
666
|
+
content: [
|
|
667
|
+
{ type: "text", text: JSON.stringify(results, null, 2) }
|
|
668
|
+
]
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
case "find_implementations": {
|
|
672
|
+
const { symbol, path: path2, limit, offset } = FindImplementationsSchema.parse(args);
|
|
673
|
+
const results = await findImplementations(
|
|
674
|
+
symbol,
|
|
675
|
+
path2,
|
|
676
|
+
limit,
|
|
677
|
+
offset
|
|
678
|
+
);
|
|
679
|
+
return {
|
|
680
|
+
content: [
|
|
681
|
+
{ type: "text", text: JSON.stringify(results, null, 2) }
|
|
682
|
+
]
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
case "get_file_structure": {
|
|
686
|
+
const { file } = GetFileStructureSchema.parse(args);
|
|
687
|
+
const structure = await getFileStructure(file);
|
|
688
|
+
return {
|
|
689
|
+
content: [
|
|
690
|
+
{ type: "text", text: JSON.stringify(structure, null, 2) }
|
|
691
|
+
]
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
case "search_code": {
|
|
695
|
+
const { pattern, path: path2, filePattern, limit } = SearchCodeSchema.parse(args);
|
|
696
|
+
const results = await searchCode(pattern, path2, filePattern, limit);
|
|
697
|
+
return {
|
|
698
|
+
content: [
|
|
699
|
+
{ type: "text", text: JSON.stringify(results, null, 2) }
|
|
700
|
+
]
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
case "get_symbol_docs": {
|
|
704
|
+
const { symbol, path: path2 } = GetSymbolDocsSchema.parse(args);
|
|
705
|
+
const docs = await getSymbolDocs(symbol, path2);
|
|
706
|
+
return {
|
|
707
|
+
content: [{ type: "text", text: JSON.stringify(docs, null, 2) }]
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
case "build_symbol_index": {
|
|
711
|
+
const { path: path2 } = BuildSymbolIndexSchema.parse(args);
|
|
712
|
+
const stats = await buildSymbolIndex(path2);
|
|
713
|
+
return {
|
|
714
|
+
content: [{ type: "text", text: JSON.stringify(stats, null, 2) }]
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
default:
|
|
718
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
719
|
+
}
|
|
720
|
+
} catch (error) {
|
|
721
|
+
return {
|
|
722
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
723
|
+
isError: true
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
async run() {
|
|
729
|
+
const transport = new StdioServerTransport();
|
|
730
|
+
await this.server.connect(transport);
|
|
731
|
+
console.error("AST Explorer MCP Server started");
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
var server = new ASTExplorerServer();
|
|
735
|
+
server.run().catch((error) => {
|
|
736
|
+
console.error("Fatal error starting AST Explorer MCP Server:", error);
|
|
737
|
+
process.exit(1);
|
|
738
|
+
});
|
|
739
|
+
export {
|
|
740
|
+
ASTExplorerServer
|
|
741
|
+
};
|
|
742
|
+
//# sourceMappingURL=index.js.map
|