@veloxts/mcp 0.6.52 → 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,35 @@
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
+
13
+ ## 0.6.54
14
+
15
+ ### Patch Changes
16
+
17
+ - feat(cli): add velox mcp init command for Claude Desktop setup
18
+ - Updated dependencies
19
+ - @veloxts/cli@0.6.54
20
+ - @veloxts/router@0.6.54
21
+ - @veloxts/validation@0.6.54
22
+
23
+ ## 0.6.53
24
+
25
+ ### Patch Changes
26
+
27
+ - feat(cli): add duplicate file detection to resource generator
28
+ - Updated dependencies
29
+ - @veloxts/cli@0.6.53
30
+ - @veloxts/router@0.6.53
31
+ - @veloxts/validation@0.6.53
32
+
3
33
  ## 0.6.52
4
34
 
5
35
  ### Patch Changes
package/README.md CHANGED
@@ -2,7 +2,109 @@
2
2
 
3
3
  > **Early Preview (v0.6.x)** - APIs are stabilizing but may still change.
4
4
 
5
- Model Context Protocol server for VeloxTS - exposes project context (procedures, schemas, routes, errors) to AI assistants like Claude for intelligent code assistance. Learn more at [@veloxts/velox](https://www.npmjs.com/package/@veloxts/velox).
5
+ Model Context Protocol server for VeloxTS - exposes project context (procedures, schemas, routes, errors) to AI assistants like Claude Desktop and other tools that support the Model Context Protocol.
6
+
7
+ ## Quick Start
8
+
9
+ ### Automatic Setup (Recommended)
10
+
11
+ The easiest way to set up the MCP server is using the VeloxTS CLI:
12
+
13
+ ```bash
14
+ velox mcp init
15
+ ```
16
+
17
+ This command will:
18
+ - Detect your operating system
19
+ - Locate your Claude Desktop configuration
20
+ - Add the VeloxTS MCP server configuration
21
+ - Guide you through the setup process
22
+
23
+ After running the command, **restart Claude Desktop** to activate the integration.
24
+
25
+ ### Manual Setup
26
+
27
+ If you prefer to configure manually, add this to your Claude Desktop configuration:
28
+
29
+ **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
30
+
31
+ **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
32
+
33
+ **Linux:** `~/.config/Claude/claude_desktop_config.json`
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "veloxts": {
39
+ "command": "npx",
40
+ "args": ["@veloxts/mcp"]
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## What It Does
47
+
48
+ The MCP server provides Claude Desktop with deep context about your VeloxTS project:
49
+
50
+ - **Procedures**: All API procedures with their inputs, outputs, and business logic
51
+ - **Schemas**: Zod validation schemas and type definitions
52
+ - **Routes**: REST endpoints and tRPC procedure mappings
53
+ - **Errors**: Custom error types and error handling patterns
54
+ - **Project Structure**: File organization and module boundaries
55
+
56
+ This enables Claude to:
57
+ - Suggest code that matches your existing patterns
58
+ - Understand your API surface and data models
59
+ - Generate procedures that fit seamlessly with your codebase
60
+ - Help debug issues with full context of your project
61
+
62
+ ## CLI Commands
63
+
64
+ ### velox mcp init
65
+
66
+ Set up Claude Desktop configuration automatically:
67
+
68
+ ```bash
69
+ velox mcp init # Interactive setup
70
+ velox mcp init --dry-run # Preview changes without writing
71
+ velox mcp init --force # Overwrite existing configuration
72
+ velox mcp init --json # Output as JSON for scripting
73
+ ```
74
+
75
+ Options:
76
+ - `--dry-run, -d`: Preview configuration changes without writing files
77
+ - `--force, -f`: Overwrite existing VeloxTS MCP configuration
78
+ - `--json`: Output results as JSON for automated workflows
79
+
80
+ ## Troubleshooting
81
+
82
+ ### Claude Desktop doesn't show VeloxTS context
83
+
84
+ 1. Ensure you've restarted Claude Desktop after running `velox mcp init`
85
+ 2. Check that the configuration file exists and is valid JSON
86
+ 3. Verify `@veloxts/mcp` is installed in your project or globally accessible
87
+ 4. Try running `npx @veloxts/mcp` manually to test the server
88
+
89
+ ### Configuration file not found
90
+
91
+ Run `velox mcp init --dry-run` to see the expected configuration path for your platform. If Claude Desktop is not installed, the command will warn you.
92
+
93
+ ### Permission errors
94
+
95
+ On macOS/Linux, ensure you have write access to the configuration directory:
96
+
97
+ ```bash
98
+ # Check permissions
99
+ ls -la ~/Library/Application\ Support/Claude # macOS
100
+ ls -la ~/.config/Claude # Linux
101
+ ```
102
+
103
+ ## Learn More
104
+
105
+ - [Model Context Protocol](https://modelcontextprotocol.io) - Official MCP specification
106
+ - [@veloxts/cli](https://www.npmjs.com/package/@veloxts/cli) - VeloxTS CLI tools
107
+ - [@veloxts/velox](https://www.npmjs.com/package/@veloxts/velox) - Complete VeloxTS framework
6
108
 
7
109
  ## License
8
110
 
@@ -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
- procedures: [],
57
- namespaces: [],
58
- totalCount: 0,
59
- queries: 0,
60
- mutations: 0,
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
- let result;
67
+ // Fallback: Dynamic discovery (works with compiled .js files or ts-node)
64
68
  try {
65
- result = await discoverProceduresVerbose(proceduresPath, {
69
+ const dynamicResult = await discoverProceduresVerbose(proceduresPath, {
66
70
  recursive: true,
67
- onInvalidExport: 'warn',
71
+ onInvalidExport: 'silent',
68
72
  });
69
- }
70
- catch {
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: 0,
75
- queries: 0,
76
- mutations: 0,
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
- const { procedures, namespaces } = extractProcedureInfo(result.collections);
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: result.scannedFiles.length,
90
- loadedFiles: result.loadedFiles.length,
91
- warnings: result.warnings.length,
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.52",
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
- "@veloxts/cli": "0.6.52",
27
- "@veloxts/validation": "0.6.52",
28
- "@veloxts/router": "0.6.52"
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"