@aiready/ast-mcp-server 0.1.1 → 0.1.2
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/README.md +45 -99
- package/dist/chunk-PRWMQQYW.js +80 -0
- package/dist/chunk-PRWMQQYW.js.map +1 -0
- package/dist/index.cjs +538 -263
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +418 -263
- package/dist/index.js.map +1 -1
- package/dist/search-code-V3LACKQ6.js +7 -0
- package/dist/search-code-V3LACKQ6.js.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,118 +1,64 @@
|
|
|
1
1
|
# @aiready/ast-mcp-server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Best-in-Class TypeScript/JavaScript Codebase Exploration for AI Agents.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This Model Context Protocol (MCP) server provides high-precision, AST-aware tools for navigating complex codebases. Unlike standard search tools, it understands imports, exports, types, and references.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## 🚀 Key Features
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- **Hybrid Indexing**: Combines `ripgrep` speed with `ts-morph` precision. Only loads relevant files into memory to handle massive monorepos.
|
|
10
|
+
- **O(1) Symbol Resolution**: Instantly find where any function, class, or type is defined using a pre-built disk cache.
|
|
11
|
+
- **Surgical Reference Finding**: Uses a two-stage lookup (Regex + AST) to find every usage of a symbol across the project without OOM crashes.
|
|
12
|
+
- **Monorepo Intelligent**: Automatically discovers `tsconfig.json` boundaries and respects TypeScript project references.
|
|
13
|
+
- **Zero Configuration**: Bundles the `ripgrep` binary and handles all TypeScript parsing out-of-the-box.
|
|
14
|
+
- **Path Security**: Hardened against path traversal and malicious agent inputs.
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
npx @smithery/cli install @aiready/ast-mcp-server
|
|
14
|
-
```
|
|
15
|
-
- **[Glama](https://glama.ai/mcp/@aiready/ast-mcp-server)**: View our listing and integration options on the Glama directory.
|
|
16
|
-
- **[Pulsar](https://gotopulsar.com)**: Find us on the Pulsar registry for MCP servers.
|
|
16
|
+
## 🛠 Tools Provided
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
| Tool | Purpose |
|
|
19
|
+
| ---------------------- | --------------------------------------------------------------- |
|
|
20
|
+
| `resolve_definition` | Find where a symbol is defined (file, line, signature, JSDoc). |
|
|
21
|
+
| `find_references` | Find all usages of a symbol across the project (paged). |
|
|
22
|
+
| `find_implementations` | Find concrete classes implementing an interface/abstract class. |
|
|
23
|
+
| `get_file_structure` | Return a structural tree of a file (classes, methods, enums). |
|
|
24
|
+
| `search_code` | Blazingly fast regex search via bundled ripgrep. |
|
|
25
|
+
| `get_symbol_docs` | Extract full JSDoc/TSDoc metadata for any symbol. |
|
|
26
|
+
| `build_symbol_index` | Warm the disk cache for a project (highly recommended). |
|
|
19
27
|
|
|
20
|
-
|
|
28
|
+
## 📦 Installation
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
```json
|
|
25
|
-
"mcpServers": {
|
|
26
|
-
"aiready-ast": {
|
|
27
|
-
"command": "npx",
|
|
28
|
-
"args": ["-y", "@aiready/ast-mcp-server"]
|
|
29
|
-
}
|
|
30
|
-
}
|
|
30
|
+
```bash
|
|
31
|
+
npx -y @aiready/ast-mcp-server
|
|
31
32
|
```
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
1. Open Cursor Settings.
|
|
36
|
-
2. Navigate to **Features** -> **MCP Servers**.
|
|
37
|
-
3. Add a new server.
|
|
38
|
-
4. Set the command to: `npx -y @aiready/ast-mcp-server`
|
|
39
|
-
|
|
40
|
-
#### Windsurf IDE
|
|
41
|
-
|
|
42
|
-
1. Open Windsurf Settings or local environment configuration.
|
|
43
|
-
2. Add a new MCP Server integration.
|
|
44
|
-
3. Configure the execution command: `npx -y @aiready/ast-mcp-server`
|
|
45
|
-
|
|
46
|
-
## Features
|
|
47
|
-
|
|
48
|
-
- **Resolve Definition**: Pinpoint exactly where symbols are defined using TypeScript's type system.
|
|
49
|
-
- **Find References**: Locate every usage of a function, class, or variable.
|
|
50
|
-
- **Find Implementations**: Discover concrete implementations of interfaces and abstract classes.
|
|
51
|
-
- **File Structure overview**: Get a high-level summary of imports, exports, and declarations.
|
|
52
|
-
- **Search Code**: Blazingly fast regex search powered by Ripgrep.
|
|
53
|
-
- **Symbol Documentation**: Instantly fetch JSDoc/TSDoc for any symbol.
|
|
54
|
-
- **Symbol Indexing**: Project-wide symbol indexing for rapid navigation.
|
|
55
|
-
|
|
56
|
-
## Tools
|
|
57
|
-
|
|
58
|
-
### 1. `resolve_definition`
|
|
59
|
-
|
|
60
|
-
Find where a symbol is defined.
|
|
61
|
-
|
|
62
|
-
- `symbol`: Name of the symbol.
|
|
63
|
-
- `path`: Project root or target directory.
|
|
64
|
-
|
|
65
|
-
### 2. `find_references`
|
|
66
|
-
|
|
67
|
-
Find all usages of a symbol.
|
|
68
|
-
|
|
69
|
-
- `symbol`: Symbol name.
|
|
70
|
-
- `path`: Project root.
|
|
71
|
-
|
|
72
|
-
### 3. `find_implementations`
|
|
34
|
+
## ⚙️ Configuration
|
|
73
35
|
|
|
74
|
-
|
|
36
|
+
### Environment Variables
|
|
75
37
|
|
|
76
|
-
- `
|
|
77
|
-
- `
|
|
38
|
+
- `AST_WORKSPACE_ROOT`: Path to the root of the allowed workspace (default: `cwd`).
|
|
39
|
+
- `AST_MAX_HEAP_MB`: Max memory allowed for AST Projects (default: `1536`).
|
|
40
|
+
- `AST_WORKER_POOL_SIZE`: Number of worker threads for parsing (default: `2`).
|
|
78
41
|
|
|
79
|
-
###
|
|
42
|
+
### Agent Configuration (Cursor/Claude Desktop)
|
|
80
43
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
Retrieve only documentation for a symbol.
|
|
96
|
-
|
|
97
|
-
- `symbol`: Symbol name.
|
|
98
|
-
- `path`: Project root.
|
|
99
|
-
|
|
100
|
-
### 7. `build_symbol_index`
|
|
101
|
-
|
|
102
|
-
Build/Warm project-wide index.
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"ast-explorer": {
|
|
48
|
+
"command": "npx",
|
|
49
|
+
"args": ["-y", "@aiready/ast-mcp-server"],
|
|
50
|
+
"env": {
|
|
51
|
+
"AST_WORKSPACE_ROOT": "/path/to/your/project"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
103
57
|
|
|
104
|
-
|
|
58
|
+
## 🛡 Security
|
|
105
59
|
|
|
106
|
-
|
|
60
|
+
All tool arguments are strictly validated. Any attempt by an agent to access files outside the `AST_WORKSPACE_ROOT` will result in a hard rejection.
|
|
107
61
|
|
|
108
|
-
|
|
109
|
-
npx @aiready/ast-mcp-server
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Development
|
|
62
|
+
## 📄 License
|
|
113
63
|
|
|
114
|
-
|
|
115
|
-
pnpm install
|
|
116
|
-
npm run build
|
|
117
|
-
npm run test
|
|
118
|
-
```
|
|
64
|
+
MIT © [AIReady](https://getaiready.dev)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// src/tools/search-code.ts
|
|
2
|
+
import { execFile } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
import { rgPath } from "@vscode/ripgrep";
|
|
5
|
+
|
|
6
|
+
// src/security.ts
|
|
7
|
+
import path from "path";
|
|
8
|
+
function resolveWorkspaceRoot() {
|
|
9
|
+
return process.env.AST_WORKSPACE_ROOT || process.cwd();
|
|
10
|
+
}
|
|
11
|
+
function validateWorkspacePath(inputPath) {
|
|
12
|
+
const root = resolveWorkspaceRoot();
|
|
13
|
+
const resolved = path.resolve(root, inputPath);
|
|
14
|
+
const normalized = path.normalize(resolved);
|
|
15
|
+
if (!normalized.startsWith(root)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Path traversal detected: ${inputPath} escapes workspace root`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
if (normalized.includes("\0")) {
|
|
21
|
+
throw new Error("Path contains null bytes");
|
|
22
|
+
}
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/tools/search-code.ts
|
|
27
|
+
var execFileAsync = promisify(execFile);
|
|
28
|
+
async function searchCode(pattern, searchPath, filePattern, limit = 50, regex = true) {
|
|
29
|
+
const safePath = validateWorkspacePath(searchPath);
|
|
30
|
+
const args = [
|
|
31
|
+
"--json",
|
|
32
|
+
"--max-count",
|
|
33
|
+
limit.toString(),
|
|
34
|
+
"--max-columns",
|
|
35
|
+
"500"
|
|
36
|
+
];
|
|
37
|
+
if (!regex) {
|
|
38
|
+
args.push("--fixed-strings");
|
|
39
|
+
}
|
|
40
|
+
args.push(pattern, safePath);
|
|
41
|
+
if (filePattern) {
|
|
42
|
+
args.push("--glob", filePattern);
|
|
43
|
+
}
|
|
44
|
+
args.push("--glob", "!**/node_modules/**");
|
|
45
|
+
args.push("--glob", "!**/dist/**");
|
|
46
|
+
args.push("--glob", "!**/.git/**");
|
|
47
|
+
try {
|
|
48
|
+
const { stdout } = await execFileAsync(rgPath, args);
|
|
49
|
+
const lines = stdout.split("\n").filter(Boolean);
|
|
50
|
+
const results = [];
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
const data = JSON.parse(line);
|
|
53
|
+
if (data.type === "match") {
|
|
54
|
+
const file = data.data.path.text;
|
|
55
|
+
const lineNumber = data.data.line_number;
|
|
56
|
+
const submatches = data.data.submatches;
|
|
57
|
+
for (const submatch of submatches) {
|
|
58
|
+
results.push({
|
|
59
|
+
file,
|
|
60
|
+
line: lineNumber,
|
|
61
|
+
column: submatch.start,
|
|
62
|
+
text: data.data.lines.text.trim()
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return results.slice(0, limit);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error.code === 1) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
validateWorkspacePath,
|
|
78
|
+
searchCode
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=chunk-PRWMQQYW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/search-code.ts","../src/security.ts"],"sourcesContent":["import { execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { rgPath } from '@vscode/ripgrep';\nimport { validateWorkspacePath } from '../security.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport interface SearchResult {\n file: string;\n line: number;\n column: number;\n text: string;\n}\n\nexport async function searchCode(\n pattern: string,\n searchPath: string,\n filePattern?: string,\n limit: number = 50,\n regex: boolean = true\n): Promise<SearchResult[]> {\n const safePath = validateWorkspacePath(searchPath);\n\n const args = [\n '--json',\n '--max-count',\n limit.toString(),\n '--max-columns',\n '500',\n ];\n\n if (!regex) {\n args.push('--fixed-strings');\n }\n\n args.push(pattern, safePath);\n\n if (filePattern) {\n args.push('--glob', filePattern);\n }\n\n // Common exclusions\n args.push('--glob', '!**/node_modules/**');\n args.push('--glob', '!**/dist/**');\n args.push('--glob', '!**/.git/**');\n\n try {\n const { stdout } = await execFileAsync(rgPath, args);\n const lines = stdout.split('\\n').filter(Boolean);\n const results: SearchResult[] = [];\n\n for (const line of lines) {\n const data = JSON.parse(line);\n if (data.type === 'match') {\n const file = data.data.path.text;\n const lineNumber = data.data.line_number;\n const submatches = data.data.submatches;\n\n for (const submatch of submatches) {\n results.push({\n file,\n line: lineNumber,\n column: submatch.start,\n text: data.data.lines.text.trim(),\n });\n }\n }\n }\n\n return results.slice(0, limit);\n } catch (error: any) {\n if (error.code === 1) {\n // rg returns 1 if no matches found\n return [];\n }\n throw error;\n }\n}\n","import path from 'path';\nimport fs from 'fs';\n\nexport function resolveWorkspaceRoot(): string {\n return process.env.AST_WORKSPACE_ROOT || process.cwd();\n}\n\nexport function validateWorkspacePath(inputPath: string): string {\n const root = resolveWorkspaceRoot();\n const resolved = path.resolve(root, inputPath);\n const normalized = path.normalize(resolved);\n\n // Reject path traversal\n if (!normalized.startsWith(root)) {\n throw new Error(\n `Path traversal detected: ${inputPath} escapes workspace root`\n );\n }\n\n // Reject null bytes\n if (normalized.includes('\\0')) {\n throw new Error('Path contains null bytes');\n }\n\n return normalized;\n}\n\nexport function validateFileExists(filePath: string): string {\n const safe = validateWorkspacePath(filePath);\n if (!fs.existsSync(safe)) {\n throw new Error(`File not found: ${filePath}`);\n }\n if (!fs.statSync(safe).isFile()) {\n throw new Error(`Not a file: ${filePath}`);\n }\n return safe;\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,SAAS,cAAc;;;ACFvB,OAAO,UAAU;AAGV,SAAS,uBAA+B;AAC7C,SAAO,QAAQ,IAAI,sBAAsB,QAAQ,IAAI;AACvD;AAEO,SAAS,sBAAsB,WAA2B;AAC/D,QAAM,OAAO,qBAAqB;AAClC,QAAM,WAAW,KAAK,QAAQ,MAAM,SAAS;AAC7C,QAAM,aAAa,KAAK,UAAU,QAAQ;AAG1C,MAAI,CAAC,WAAW,WAAW,IAAI,GAAG;AAChC,UAAM,IAAI;AAAA,MACR,4BAA4B,SAAS;AAAA,IACvC;AAAA,EACF;AAGA,MAAI,WAAW,SAAS,IAAI,GAAG;AAC7B,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAEA,SAAO;AACT;;;ADpBA,IAAM,gBAAgB,UAAU,QAAQ;AASxC,eAAsB,WACpB,SACA,YACA,aACA,QAAgB,IAChB,QAAiB,MACQ;AACzB,QAAM,WAAW,sBAAsB,UAAU;AAEjD,QAAM,OAAO;AAAA,IACX;AAAA,IACA;AAAA,IACA,MAAM,SAAS;AAAA,IACf;AAAA,IACA;AAAA,EACF;AAEA,MAAI,CAAC,OAAO;AACV,SAAK,KAAK,iBAAiB;AAAA,EAC7B;AAEA,OAAK,KAAK,SAAS,QAAQ;AAE3B,MAAI,aAAa;AACf,SAAK,KAAK,UAAU,WAAW;AAAA,EACjC;AAGA,OAAK,KAAK,UAAU,qBAAqB;AACzC,OAAK,KAAK,UAAU,aAAa;AACjC,OAAK,KAAK,UAAU,aAAa;AAEjC,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,MAAM,cAAc,QAAQ,IAAI;AACnD,UAAM,QAAQ,OAAO,MAAM,IAAI,EAAE,OAAO,OAAO;AAC/C,UAAM,UAA0B,CAAC;AAEjC,eAAW,QAAQ,OAAO;AACxB,YAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,UAAI,KAAK,SAAS,SAAS;AACzB,cAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,cAAM,aAAa,KAAK,KAAK;AAC7B,cAAM,aAAa,KAAK,KAAK;AAE7B,mBAAW,YAAY,YAAY;AACjC,kBAAQ,KAAK;AAAA,YACX;AAAA,YACA,MAAM;AAAA,YACN,QAAQ,SAAS;AAAA,YACjB,MAAM,KAAK,KAAK,MAAM,KAAK,KAAK;AAAA,UAClC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,WAAO,QAAQ,MAAM,GAAG,KAAK;AAAA,EAC/B,SAAS,OAAY;AACnB,QAAI,MAAM,SAAS,GAAG;AAEpB,aAAO,CAAC;AAAA,IACV;AACA,UAAM;AAAA,EACR;AACF;","names":[]}
|