@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 +30 -0
- package/README.md +103 -1
- package/dist/resources/procedures.d.ts +4 -0
- package/dist/resources/procedures.js +62 -22
- package/dist/resources/static-analyzer.d.ts +38 -0
- package/dist/resources/static-analyzer.js +377 -0
- package/package.json +5 -4
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
|
|
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
|
-
|
|
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/router": "0.6.
|
|
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"
|