@veloxts/mcp 0.6.54 → 0.6.55
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
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @veloxts/mcp
|
|
2
2
|
|
|
3
|
+
## 0.6.55
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat(mcp): add static TypeScript analyzer for procedure discovery
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @veloxts/cli@0.6.55
|
|
10
|
+
- @veloxts/router@0.6.55
|
|
11
|
+
- @veloxts/validation@0.6.55
|
|
12
|
+
|
|
3
13
|
## 0.6.54
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Procedures Resource
|
|
3
3
|
*
|
|
4
4
|
* Exposes VeloxTS procedure information to AI tools.
|
|
5
|
+
* Uses dynamic discovery when possible, falls back to static analysis for TypeScript files.
|
|
5
6
|
*/
|
|
6
7
|
/**
|
|
7
8
|
* Information about a single procedure
|
|
@@ -44,6 +45,9 @@ export interface ProceduresResourceResponse {
|
|
|
44
45
|
}
|
|
45
46
|
/**
|
|
46
47
|
* Discover and return procedure information for a project
|
|
48
|
+
*
|
|
49
|
+
* Uses static TypeScript analysis as primary method (works with uncompiled TS),
|
|
50
|
+
* falls back to dynamic discovery for compiled projects.
|
|
47
51
|
*/
|
|
48
52
|
export declare function getProcedures(projectRoot: string): Promise<ProceduresResourceResponse>;
|
|
49
53
|
/**
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* Procedures Resource
|
|
3
3
|
*
|
|
4
4
|
* Exposes VeloxTS procedure information to AI tools.
|
|
5
|
+
* Uses dynamic discovery when possible, falls back to static analysis for TypeScript files.
|
|
5
6
|
*/
|
|
6
7
|
import { discoverProceduresVerbose, getRouteSummary } from '@veloxts/router';
|
|
7
8
|
import { getProceduresPath } from '../utils/project.js';
|
|
9
|
+
import { analyzeDirectory } from './static-analyzer.js';
|
|
8
10
|
// ============================================================================
|
|
9
11
|
// Resource Handler
|
|
10
12
|
// ============================================================================
|
|
@@ -48,47 +50,85 @@ function extractProcedureInfo(collections) {
|
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* Discover and return procedure information for a project
|
|
53
|
+
*
|
|
54
|
+
* Uses static TypeScript analysis as primary method (works with uncompiled TS),
|
|
55
|
+
* falls back to dynamic discovery for compiled projects.
|
|
51
56
|
*/
|
|
52
57
|
export async function getProcedures(projectRoot) {
|
|
53
58
|
const proceduresPath = getProceduresPath(projectRoot);
|
|
54
59
|
if (!proceduresPath) {
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
60
|
+
return emptyResponse();
|
|
61
|
+
}
|
|
62
|
+
// Primary: Static TypeScript analysis (works with uncompiled .ts files)
|
|
63
|
+
const staticResult = analyzeDirectory(proceduresPath);
|
|
64
|
+
if (staticResult.procedures.length > 0) {
|
|
65
|
+
return formatStaticResult(staticResult);
|
|
62
66
|
}
|
|
63
|
-
|
|
67
|
+
// Fallback: Dynamic discovery (works with compiled .js files or ts-node)
|
|
64
68
|
try {
|
|
65
|
-
|
|
69
|
+
const dynamicResult = await discoverProceduresVerbose(proceduresPath, {
|
|
66
70
|
recursive: true,
|
|
67
|
-
onInvalidExport: '
|
|
71
|
+
onInvalidExport: 'silent',
|
|
68
72
|
});
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
const { procedures, namespaces } = extractProcedureInfo(dynamicResult.collections);
|
|
74
|
+
const queries = procedures.filter((p) => p.type === 'query').length;
|
|
75
|
+
const mutations = procedures.filter((p) => p.type === 'mutation').length;
|
|
71
76
|
return {
|
|
72
|
-
procedures
|
|
73
|
-
namespaces
|
|
74
|
-
totalCount:
|
|
75
|
-
queries
|
|
76
|
-
mutations
|
|
77
|
+
procedures,
|
|
78
|
+
namespaces,
|
|
79
|
+
totalCount: procedures.length,
|
|
80
|
+
queries,
|
|
81
|
+
mutations,
|
|
82
|
+
discoveryInfo: {
|
|
83
|
+
scannedFiles: dynamicResult.scannedFiles.length,
|
|
84
|
+
loadedFiles: dynamicResult.loadedFiles.length,
|
|
85
|
+
warnings: dynamicResult.warnings.length,
|
|
86
|
+
},
|
|
77
87
|
};
|
|
78
88
|
}
|
|
79
|
-
|
|
89
|
+
catch {
|
|
90
|
+
// Dynamic discovery failed (expected for uncompiled TS projects)
|
|
91
|
+
}
|
|
92
|
+
return emptyResponse();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create empty response
|
|
96
|
+
*/
|
|
97
|
+
function emptyResponse() {
|
|
98
|
+
return {
|
|
99
|
+
procedures: [],
|
|
100
|
+
namespaces: [],
|
|
101
|
+
totalCount: 0,
|
|
102
|
+
queries: 0,
|
|
103
|
+
mutations: 0,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Format static analysis result as ProceduresResourceResponse
|
|
108
|
+
*/
|
|
109
|
+
function formatStaticResult(staticResult) {
|
|
110
|
+
const procedures = staticResult.procedures.map((p) => ({
|
|
111
|
+
name: p.name,
|
|
112
|
+
namespace: p.namespace,
|
|
113
|
+
type: p.type === 'unknown' ? 'query' : p.type, // Default unknown to query
|
|
114
|
+
hasInputSchema: p.hasInputSchema,
|
|
115
|
+
hasOutputSchema: p.hasOutputSchema,
|
|
116
|
+
guardCount: p.hasGuards ? 1 : 0,
|
|
117
|
+
middlewareCount: p.hasMiddleware ? 1 : 0,
|
|
118
|
+
route: p.route,
|
|
119
|
+
}));
|
|
80
120
|
const queries = procedures.filter((p) => p.type === 'query').length;
|
|
81
121
|
const mutations = procedures.filter((p) => p.type === 'mutation').length;
|
|
82
122
|
return {
|
|
83
123
|
procedures,
|
|
84
|
-
namespaces,
|
|
124
|
+
namespaces: staticResult.namespaces,
|
|
85
125
|
totalCount: procedures.length,
|
|
86
126
|
queries,
|
|
87
127
|
mutations,
|
|
88
128
|
discoveryInfo: {
|
|
89
|
-
scannedFiles:
|
|
90
|
-
loadedFiles:
|
|
91
|
-
warnings:
|
|
129
|
+
scannedFiles: staticResult.files.length,
|
|
130
|
+
loadedFiles: staticResult.files.length,
|
|
131
|
+
warnings: staticResult.errors.length,
|
|
92
132
|
},
|
|
93
133
|
};
|
|
94
134
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static TypeScript Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Extracts procedure information from TypeScript source files without execution.
|
|
5
|
+
* Uses TypeScript Compiler API in parse-only mode for accurate AST analysis.
|
|
6
|
+
* Falls back to regex for edge cases.
|
|
7
|
+
*/
|
|
8
|
+
export interface StaticProcedureInfo {
|
|
9
|
+
name: string;
|
|
10
|
+
namespace: string;
|
|
11
|
+
type: 'query' | 'mutation' | 'unknown';
|
|
12
|
+
hasInputSchema: boolean;
|
|
13
|
+
hasOutputSchema: boolean;
|
|
14
|
+
hasGuards: boolean;
|
|
15
|
+
hasMiddleware: boolean;
|
|
16
|
+
route?: {
|
|
17
|
+
method: string;
|
|
18
|
+
path: string;
|
|
19
|
+
};
|
|
20
|
+
restOverride?: {
|
|
21
|
+
method?: string;
|
|
22
|
+
path?: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface StaticAnalysisResult {
|
|
26
|
+
procedures: StaticProcedureInfo[];
|
|
27
|
+
namespaces: string[];
|
|
28
|
+
files: string[];
|
|
29
|
+
errors: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Analyze a procedures directory statically
|
|
33
|
+
*/
|
|
34
|
+
export declare function analyzeDirectory(proceduresPath: string): StaticAnalysisResult;
|
|
35
|
+
/**
|
|
36
|
+
* Format static analysis result as text
|
|
37
|
+
*/
|
|
38
|
+
export declare function formatStaticAnalysisAsText(result: StaticAnalysisResult): string;
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static TypeScript Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Extracts procedure information from TypeScript source files without execution.
|
|
5
|
+
* Uses TypeScript Compiler API in parse-only mode for accurate AST analysis.
|
|
6
|
+
* Falls back to regex for edge cases.
|
|
7
|
+
*/
|
|
8
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
9
|
+
import { basename, extname, join } from 'node:path';
|
|
10
|
+
import ts from 'typescript';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Procedure Name to HTTP Method Mapping
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const METHOD_PREFIXES = {
|
|
15
|
+
get: { method: 'GET', type: 'query' },
|
|
16
|
+
list: { method: 'GET', type: 'query' },
|
|
17
|
+
find: { method: 'GET', type: 'query' },
|
|
18
|
+
search: { method: 'GET', type: 'query' },
|
|
19
|
+
create: { method: 'POST', type: 'mutation' },
|
|
20
|
+
add: { method: 'POST', type: 'mutation' },
|
|
21
|
+
update: { method: 'PUT', type: 'mutation' },
|
|
22
|
+
edit: { method: 'PUT', type: 'mutation' },
|
|
23
|
+
patch: { method: 'PATCH', type: 'mutation' },
|
|
24
|
+
delete: { method: 'DELETE', type: 'mutation' },
|
|
25
|
+
remove: { method: 'DELETE', type: 'mutation' },
|
|
26
|
+
};
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Main Analysis Functions
|
|
29
|
+
// ============================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Analyze a procedures directory statically
|
|
32
|
+
*/
|
|
33
|
+
export function analyzeDirectory(proceduresPath) {
|
|
34
|
+
const result = {
|
|
35
|
+
procedures: [],
|
|
36
|
+
namespaces: [],
|
|
37
|
+
files: [],
|
|
38
|
+
errors: [],
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
const entries = readdirSync(proceduresPath);
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const fullPath = join(proceduresPath, entry);
|
|
44
|
+
try {
|
|
45
|
+
const stat = statSync(fullPath);
|
|
46
|
+
if (stat.isFile() && isTypeScriptFile(entry) && !isExcluded(entry)) {
|
|
47
|
+
result.files.push(fullPath);
|
|
48
|
+
try {
|
|
49
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
50
|
+
const collections = analyzeFileWithAST(fullPath, content);
|
|
51
|
+
for (const collection of collections) {
|
|
52
|
+
if (!result.namespaces.includes(collection.namespace)) {
|
|
53
|
+
result.namespaces.push(collection.namespace);
|
|
54
|
+
}
|
|
55
|
+
for (const proc of collection.procedures) {
|
|
56
|
+
const info = toProcedureInfo(proc, collection.namespace);
|
|
57
|
+
result.procedures.push(info);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
result.errors.push(`Error analyzing ${entry}: ${err instanceof Error ? err.message : String(err)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
result.errors.push(`Error accessing ${entry}: ${err instanceof Error ? err.message : String(err)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
result.errors.push(`Error reading directory: ${err instanceof Error ? err.message : String(err)}`);
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// TypeScript Compiler API Analysis
|
|
78
|
+
// ============================================================================
|
|
79
|
+
/**
|
|
80
|
+
* Parse a TypeScript file and extract procedure information using the TS Compiler API.
|
|
81
|
+
* Uses parse-only mode (no type checking) for speed and to avoid import resolution.
|
|
82
|
+
*/
|
|
83
|
+
function analyzeFileWithAST(filePath, content) {
|
|
84
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, // setParentNodes - needed for tree traversal
|
|
85
|
+
ts.ScriptKind.TS);
|
|
86
|
+
const collections = [];
|
|
87
|
+
// Visit all nodes looking for procedures() or defineProcedures() calls
|
|
88
|
+
function visit(node) {
|
|
89
|
+
if (ts.isCallExpression(node)) {
|
|
90
|
+
const collection = tryExtractProcedureCollection(node);
|
|
91
|
+
if (collection) {
|
|
92
|
+
collections.push({ ...collection, filePath });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
ts.forEachChild(node, visit);
|
|
96
|
+
}
|
|
97
|
+
visit(sourceFile);
|
|
98
|
+
// If no collections found via AST, try regex fallback
|
|
99
|
+
if (collections.length === 0) {
|
|
100
|
+
const regexResult = analyzeFileWithRegex(filePath, content);
|
|
101
|
+
if (regexResult) {
|
|
102
|
+
collections.push(regexResult);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return collections;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Try to extract a procedure collection from a call expression.
|
|
109
|
+
* Looks for: procedures('namespace', { ... }) or defineProcedures('namespace', { ... })
|
|
110
|
+
*/
|
|
111
|
+
function tryExtractProcedureCollection(node) {
|
|
112
|
+
// Check if this is a procedures() or defineProcedures() call
|
|
113
|
+
const callee = node.expression;
|
|
114
|
+
let functionName;
|
|
115
|
+
if (ts.isIdentifier(callee)) {
|
|
116
|
+
functionName = callee.text;
|
|
117
|
+
}
|
|
118
|
+
if (functionName !== 'procedures' && functionName !== 'defineProcedures') {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
// Extract namespace from first argument
|
|
122
|
+
const [namespaceArg, proceduresArg] = node.arguments;
|
|
123
|
+
if (!namespaceArg || !ts.isStringLiteral(namespaceArg)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const namespace = namespaceArg.text;
|
|
127
|
+
// Extract procedures from second argument (object literal)
|
|
128
|
+
if (!proceduresArg || !ts.isObjectLiteralExpression(proceduresArg)) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const procedures = [];
|
|
132
|
+
for (const prop of proceduresArg.properties) {
|
|
133
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
134
|
+
const procedureName = prop.name.text;
|
|
135
|
+
const procedureInfo = extractProcedureInfo(prop.initializer);
|
|
136
|
+
procedures.push({
|
|
137
|
+
name: procedureName,
|
|
138
|
+
...procedureInfo,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { namespace, procedures };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Extract procedure information from a procedure builder chain.
|
|
146
|
+
* Handles: procedure().input(...).output(...).guard(...).query/mutation(...)
|
|
147
|
+
*/
|
|
148
|
+
function extractProcedureInfo(node) {
|
|
149
|
+
const info = {
|
|
150
|
+
type: 'unknown',
|
|
151
|
+
hasInput: false,
|
|
152
|
+
hasOutput: false,
|
|
153
|
+
hasGuard: false,
|
|
154
|
+
hasMiddleware: false,
|
|
155
|
+
};
|
|
156
|
+
// Walk the call chain
|
|
157
|
+
function walkChain(n) {
|
|
158
|
+
if (!ts.isCallExpression(n))
|
|
159
|
+
return;
|
|
160
|
+
const callee = n.expression;
|
|
161
|
+
// Check for method calls on the chain
|
|
162
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
163
|
+
const methodName = callee.name.text;
|
|
164
|
+
switch (methodName) {
|
|
165
|
+
case 'query':
|
|
166
|
+
info.type = 'query';
|
|
167
|
+
break;
|
|
168
|
+
case 'mutation':
|
|
169
|
+
info.type = 'mutation';
|
|
170
|
+
break;
|
|
171
|
+
case 'input':
|
|
172
|
+
info.hasInput = true;
|
|
173
|
+
break;
|
|
174
|
+
case 'output':
|
|
175
|
+
info.hasOutput = true;
|
|
176
|
+
break;
|
|
177
|
+
case 'guard':
|
|
178
|
+
info.hasGuard = true;
|
|
179
|
+
break;
|
|
180
|
+
case 'use':
|
|
181
|
+
info.hasMiddleware = true;
|
|
182
|
+
break;
|
|
183
|
+
case 'rest':
|
|
184
|
+
info.restOverride = extractRestOverride(n.arguments[0]);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
// Continue walking up the chain
|
|
188
|
+
walkChain(callee.expression);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
walkChain(node);
|
|
192
|
+
return info;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Extract REST override configuration from .rest({ method, path }) call
|
|
196
|
+
*/
|
|
197
|
+
function extractRestOverride(arg) {
|
|
198
|
+
if (!arg || !ts.isObjectLiteralExpression(arg)) {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
const result = {};
|
|
202
|
+
for (const prop of arg.properties) {
|
|
203
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
204
|
+
const key = prop.name.text;
|
|
205
|
+
if ((key === 'method' || key === 'path') && ts.isStringLiteral(prop.initializer)) {
|
|
206
|
+
result[key] = prop.initializer.text;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
211
|
+
}
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// Regex Fallback Analysis
|
|
214
|
+
// ============================================================================
|
|
215
|
+
/**
|
|
216
|
+
* Fallback regex-based analysis for files that don't match the standard pattern
|
|
217
|
+
*/
|
|
218
|
+
function analyzeFileWithRegex(filePath, content) {
|
|
219
|
+
const procedures = [];
|
|
220
|
+
let namespace = '';
|
|
221
|
+
// Extract namespace from procedures() call
|
|
222
|
+
const namespaceMatch = content.match(/procedures\s*\(\s*['"]([^'"]+)['"]/);
|
|
223
|
+
if (namespaceMatch) {
|
|
224
|
+
namespace = namespaceMatch[1];
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
// Derive from filename
|
|
228
|
+
const filename = basename(filePath, extname(filePath));
|
|
229
|
+
if (filename !== 'index') {
|
|
230
|
+
namespace = filename;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!namespace) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
// Extract procedure names using regex
|
|
237
|
+
const procedurePattern = /(\w+)\s*:\s*procedure\s*[.(]/g;
|
|
238
|
+
const matches = content.matchAll(procedurePattern);
|
|
239
|
+
for (const match of matches) {
|
|
240
|
+
const name = match[1];
|
|
241
|
+
// Determine type from naming convention
|
|
242
|
+
let type = 'unknown';
|
|
243
|
+
for (const [prefix, info] of Object.entries(METHOD_PREFIXES)) {
|
|
244
|
+
if (name.toLowerCase().startsWith(prefix)) {
|
|
245
|
+
type = info.type;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Check for explicit .query() or .mutation()
|
|
250
|
+
if (content.includes(`${name}`) && content.includes('.query(')) {
|
|
251
|
+
type = 'query';
|
|
252
|
+
}
|
|
253
|
+
else if (content.includes(`${name}`) && content.includes('.mutation(')) {
|
|
254
|
+
type = 'mutation';
|
|
255
|
+
}
|
|
256
|
+
procedures.push({
|
|
257
|
+
name,
|
|
258
|
+
type,
|
|
259
|
+
hasInput: content.includes('.input('),
|
|
260
|
+
hasOutput: content.includes('.output('),
|
|
261
|
+
hasGuard: content.includes('.guard('),
|
|
262
|
+
hasMiddleware: content.includes('.use('),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (procedures.length === 0) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
return { namespace, procedures, filePath };
|
|
269
|
+
}
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Helpers
|
|
272
|
+
// ============================================================================
|
|
273
|
+
/**
|
|
274
|
+
* Convert parsed procedure to StaticProcedureInfo
|
|
275
|
+
*/
|
|
276
|
+
function toProcedureInfo(proc, namespace) {
|
|
277
|
+
const route = inferRestRoute(proc.name, namespace, proc.restOverride);
|
|
278
|
+
return {
|
|
279
|
+
name: proc.name,
|
|
280
|
+
namespace,
|
|
281
|
+
type: proc.type,
|
|
282
|
+
hasInputSchema: proc.hasInput,
|
|
283
|
+
hasOutputSchema: proc.hasOutput,
|
|
284
|
+
hasGuards: proc.hasGuard,
|
|
285
|
+
hasMiddleware: proc.hasMiddleware,
|
|
286
|
+
route,
|
|
287
|
+
restOverride: proc.restOverride,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Infer REST route from procedure name and namespace
|
|
292
|
+
*/
|
|
293
|
+
function inferRestRoute(procedureName, namespace, override) {
|
|
294
|
+
const basePath = `/api/${namespace}`;
|
|
295
|
+
// Use override if provided
|
|
296
|
+
if (override?.method && override?.path) {
|
|
297
|
+
return { method: override.method, path: override.path };
|
|
298
|
+
}
|
|
299
|
+
// Infer from naming convention
|
|
300
|
+
for (const [prefix, info] of Object.entries(METHOD_PREFIXES)) {
|
|
301
|
+
if (procedureName.toLowerCase().startsWith(prefix)) {
|
|
302
|
+
// Collection endpoints (no :id)
|
|
303
|
+
if (['list', 'find', 'search', 'create', 'add'].includes(prefix)) {
|
|
304
|
+
return {
|
|
305
|
+
method: override?.method || info.method,
|
|
306
|
+
path: override?.path || basePath,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// Resource endpoints (with :id)
|
|
310
|
+
return {
|
|
311
|
+
method: override?.method || info.method,
|
|
312
|
+
path: override?.path || `${basePath}/:id`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Default to GET collection
|
|
317
|
+
return { method: 'GET', path: basePath };
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Check if file is a TypeScript file
|
|
321
|
+
*/
|
|
322
|
+
function isTypeScriptFile(filename) {
|
|
323
|
+
const ext = extname(filename);
|
|
324
|
+
return ['.ts', '.tsx', '.mts'].includes(ext);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Check if file should be excluded
|
|
328
|
+
*/
|
|
329
|
+
function isExcluded(filename) {
|
|
330
|
+
return (filename.startsWith('_') ||
|
|
331
|
+
filename.endsWith('.test.ts') ||
|
|
332
|
+
filename.endsWith('.spec.ts') ||
|
|
333
|
+
filename.endsWith('.d.ts') ||
|
|
334
|
+
filename === 'index.ts');
|
|
335
|
+
}
|
|
336
|
+
// ============================================================================
|
|
337
|
+
// Formatting
|
|
338
|
+
// ============================================================================
|
|
339
|
+
/**
|
|
340
|
+
* Format static analysis result as text
|
|
341
|
+
*/
|
|
342
|
+
export function formatStaticAnalysisAsText(result) {
|
|
343
|
+
const lines = [
|
|
344
|
+
'# VeloxTS Procedures',
|
|
345
|
+
'',
|
|
346
|
+
`Total: ${result.procedures.length} procedures`,
|
|
347
|
+
`Namespaces: ${result.namespaces.join(', ') || 'none'}`,
|
|
348
|
+
`Files analyzed: ${result.files.length}`,
|
|
349
|
+
'',
|
|
350
|
+
];
|
|
351
|
+
if (result.errors.length > 0) {
|
|
352
|
+
lines.push('## Analysis Notes');
|
|
353
|
+
for (const error of result.errors) {
|
|
354
|
+
lines.push(`- ${error}`);
|
|
355
|
+
}
|
|
356
|
+
lines.push('');
|
|
357
|
+
}
|
|
358
|
+
// Group by namespace
|
|
359
|
+
const byNamespace = new Map();
|
|
360
|
+
for (const proc of result.procedures) {
|
|
361
|
+
const list = byNamespace.get(proc.namespace) ?? [];
|
|
362
|
+
list.push(proc);
|
|
363
|
+
byNamespace.set(proc.namespace, list);
|
|
364
|
+
}
|
|
365
|
+
for (const [namespace, procs] of byNamespace) {
|
|
366
|
+
lines.push(`## ${namespace}`);
|
|
367
|
+
lines.push('');
|
|
368
|
+
for (const proc of procs) {
|
|
369
|
+
const type = proc.type === 'query' ? 'Q' : proc.type === 'mutation' ? 'M' : '?';
|
|
370
|
+
const route = proc.route ? ` -> ${proc.route.method} ${proc.route.path}` : '';
|
|
371
|
+
const guards = proc.hasGuards ? ' [guarded]' : '';
|
|
372
|
+
lines.push(`- [${type}] ${proc.name}${route}${guards}`);
|
|
373
|
+
}
|
|
374
|
+
lines.push('');
|
|
375
|
+
}
|
|
376
|
+
return lines.join('\n');
|
|
377
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veloxts/mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.55",
|
|
4
4
|
"description": "Model Context Protocol server for VeloxTS - expose project context to AI tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@modelcontextprotocol/sdk": "1.25.1",
|
|
26
|
-
"
|
|
27
|
-
"@veloxts/
|
|
28
|
-
"@veloxts/
|
|
26
|
+
"typescript": "5.9.3",
|
|
27
|
+
"@veloxts/cli": "0.6.55",
|
|
28
|
+
"@veloxts/router": "0.6.55",
|
|
29
|
+
"@veloxts/validation": "0.6.55"
|
|
29
30
|
},
|
|
30
31
|
"peerDependencies": {
|
|
31
32
|
"zod": ">=3.25.0"
|