@veloxts/mcp 0.7.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @veloxts/mcp
2
2
 
3
+ ## 0.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - chore(auth,core,create,cli,client,orm,mcp,router,validation,web): simplify code for clarity and maintainability
8
+ - Updated dependencies
9
+ - @veloxts/cli@0.7.2
10
+ - @veloxts/router@0.7.2
11
+ - @veloxts/validation@0.7.2
12
+
13
+ ## 0.7.1
14
+
15
+ ### Patch Changes
16
+
17
+ - security audit, bumps dependency packages
18
+ - Updated dependencies
19
+ - @veloxts/cli@0.7.1
20
+ - @veloxts/router@0.7.1
21
+ - @veloxts/validation@0.7.1
22
+
3
23
  ## 0.7.0
4
24
 
5
25
  ### Minor Changes
@@ -30,17 +30,23 @@ function getErrorCategory(code) {
30
30
  return category?.name ?? 'Unknown';
31
31
  }
32
32
  /**
33
- * Get all errors from the catalog
33
+ * Convert a catalog error definition to an ErrorInfo
34
34
  */
35
- export function getErrors() {
36
- const errors = Object.values(ERROR_CATALOG).map((def) => ({
35
+ function toErrorInfo(def) {
36
+ return {
37
37
  code: def.code,
38
38
  name: def.name,
39
39
  message: def.message,
40
40
  fix: def.fix,
41
41
  docsUrl: def.docsUrl,
42
42
  category: getErrorCategory(def.code),
43
- }));
43
+ };
44
+ }
45
+ /**
46
+ * Get all errors from the catalog
47
+ */
48
+ export function getErrors() {
49
+ const errors = Object.values(ERROR_CATALOG).map(toErrorInfo);
44
50
  const categories = ERROR_CATEGORIES.map((cat) => ({
45
51
  prefix: cat.prefix,
46
52
  name: cat.name,
@@ -56,15 +62,7 @@ export function getErrors() {
56
62
  * Get errors for a specific category
57
63
  */
58
64
  export function getErrorsByPrefix(prefix) {
59
- const defs = getErrorsByCategory(prefix);
60
- return defs.map((def) => ({
61
- code: def.code,
62
- name: def.name,
63
- message: def.message,
64
- fix: def.fix,
65
- docsUrl: def.docsUrl,
66
- category: getErrorCategory(def.code),
67
- }));
65
+ return getErrorsByCategory(prefix).map(toErrorInfo);
68
66
  }
69
67
  /**
70
68
  * Search errors by keyword
@@ -76,14 +74,7 @@ export function searchErrors(query) {
76
74
  def.name.toLowerCase().includes(lowerQuery) ||
77
75
  def.message.toLowerCase().includes(lowerQuery) ||
78
76
  def.fix?.toLowerCase().includes(lowerQuery))
79
- .map((def) => ({
80
- code: def.code,
81
- name: def.name,
82
- message: def.message,
83
- fix: def.fix,
84
- docsUrl: def.docsUrl,
85
- category: getErrorCategory(def.code),
86
- }));
77
+ .map(toErrorInfo);
87
78
  }
88
79
  /**
89
80
  * Format errors response as text
@@ -395,7 +395,17 @@ export function formatStaticAnalysisAsText(result) {
395
395
  lines.push(`## ${namespace}`);
396
396
  lines.push('');
397
397
  for (const proc of procs) {
398
- const type = proc.type === 'query' ? 'Q' : proc.type === 'mutation' ? 'M' : '?';
398
+ let type;
399
+ switch (proc.type) {
400
+ case 'query':
401
+ type = 'Q';
402
+ break;
403
+ case 'mutation':
404
+ type = 'M';
405
+ break;
406
+ default:
407
+ type = '?';
408
+ }
399
409
  const route = proc.route ? ` -> ${proc.route.method} ${proc.route.path}` : '';
400
410
  const guards = proc.hasGuards ? ' [guarded]' : '';
401
411
  lines.push(`- [${type}] ${proc.name}${route}${guards}`);
package/dist/server.js CHANGED
@@ -105,57 +105,28 @@ export function createVeloxMCPServer(options = {}) {
105
105
  ],
106
106
  };
107
107
  });
108
+ // Helper to wrap text content as an MCP resource response
109
+ function textResource(uri, text) {
110
+ return { contents: [{ uri, mimeType: 'text/plain', text }] };
111
+ }
108
112
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
109
113
  const uri = request.params.uri;
110
114
  log(`Reading resource: ${uri}`);
111
115
  switch (uri) {
112
116
  case 'velox://procedures': {
113
117
  const data = await getProcedures(projectRoot);
114
- return {
115
- contents: [
116
- {
117
- uri,
118
- mimeType: 'text/plain',
119
- text: formatProceduresAsText(data),
120
- },
121
- ],
122
- };
118
+ return textResource(uri, formatProceduresAsText(data));
123
119
  }
124
120
  case 'velox://routes': {
125
121
  const data = await getRoutes(projectRoot);
126
- return {
127
- contents: [
128
- {
129
- uri,
130
- mimeType: 'text/plain',
131
- text: formatRoutesAsText(data),
132
- },
133
- ],
134
- };
122
+ return textResource(uri, formatRoutesAsText(data));
135
123
  }
136
124
  case 'velox://schemas': {
137
125
  const data = await getSchemas(projectRoot);
138
- return {
139
- contents: [
140
- {
141
- uri,
142
- mimeType: 'text/plain',
143
- text: formatSchemasAsText(data),
144
- },
145
- ],
146
- };
126
+ return textResource(uri, formatSchemasAsText(data));
147
127
  }
148
128
  case 'velox://errors': {
149
- const data = getErrors();
150
- return {
151
- contents: [
152
- {
153
- uri,
154
- mimeType: 'text/plain',
155
- text: formatErrorsAsText(data),
156
- },
157
- ],
158
- };
129
+ return textResource(uri, formatErrorsAsText(getErrors()));
159
130
  }
160
131
  case 'velox://project': {
161
132
  const info = await getProjectInfo(projectRoot);
@@ -177,15 +148,7 @@ export function createVeloxMCPServer(options = {}) {
177
148
  .filter(Boolean)
178
149
  .join('\n')
179
150
  : 'No VeloxTS project detected in current directory.';
180
- return {
181
- contents: [
182
- {
183
- uri,
184
- mimeType: 'text/plain',
185
- text,
186
- },
187
- ],
188
- };
151
+ return textResource(uri, text);
189
152
  }
190
153
  case 'velox://auth-context': {
191
154
  const text = `# Authentication Context (ctx.user)
@@ -277,15 +240,7 @@ const fullUser = await getFullUser(ctx);
277
240
  const orgId = fullUser.organizationId;
278
241
  \`\`\`
279
242
  `;
280
- return {
281
- contents: [
282
- {
283
- uri,
284
- mimeType: 'text/plain',
285
- text,
286
- },
287
- ],
288
- };
243
+ return textResource(uri, text);
289
244
  }
290
245
  default:
291
246
  throw new Error(`Unknown resource: ${uri}`);
@@ -363,21 +318,20 @@ const orgId = fullUser.organizationId;
363
318
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
364
319
  const { name, arguments: args } = request.params;
365
320
  log(`Calling tool: ${name}`);
321
+ // Helper to build a tool response
322
+ function toolResult(text, isError = false) {
323
+ return { content: [{ type: 'text', text }], isError };
324
+ }
325
+ // Helper to format Zod validation errors into a tool error response
326
+ function validationError(toolName, issues) {
327
+ const errors = issues.map((e) => `${e.path.join('.')}: ${e.message}`);
328
+ return toolResult(`Invalid arguments for ${toolName}:\n${errors.join('\n')}`, true);
329
+ }
366
330
  switch (name) {
367
331
  case 'velox_generate': {
368
- // Validate arguments with Zod
369
332
  const parsed = GenerateArgsSchema.safeParse(args);
370
333
  if (!parsed.success) {
371
- const errors = parsed.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);
372
- return {
373
- content: [
374
- {
375
- type: 'text',
376
- text: `Invalid arguments for velox_generate:\n${errors.join('\n')}`,
377
- },
378
- ],
379
- isError: true,
380
- };
334
+ return validationError('velox_generate', parsed.error.issues);
381
335
  }
382
336
  const result = await generate({
383
337
  type: parsed.data.type,
@@ -386,30 +340,12 @@ const orgId = fullUser.organizationId;
386
340
  dryRun: parsed.data.dryRun,
387
341
  json: true,
388
342
  }, projectRoot);
389
- return {
390
- content: [
391
- {
392
- type: 'text',
393
- text: formatGenerateResult(result),
394
- },
395
- ],
396
- isError: !result.success,
397
- };
343
+ return toolResult(formatGenerateResult(result), !result.success);
398
344
  }
399
345
  case 'velox_migrate': {
400
- // Validate arguments with Zod
401
346
  const parsed = MigrateArgsSchema.safeParse(args);
402
347
  if (!parsed.success) {
403
- const errors = parsed.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`);
404
- return {
405
- content: [
406
- {
407
- type: 'text',
408
- text: `Invalid arguments for velox_migrate:\n${errors.join('\n')}`,
409
- },
410
- ],
411
- isError: true,
412
- };
348
+ return validationError('velox_migrate', parsed.error.issues);
413
349
  }
414
350
  const result = await migrate({
415
351
  action: parsed.data.action,
@@ -417,15 +353,7 @@ const orgId = fullUser.organizationId;
417
353
  dryRun: parsed.data.dryRun,
418
354
  json: true,
419
355
  }, projectRoot);
420
- return {
421
- content: [
422
- {
423
- type: 'text',
424
- text: formatMigrateResult(result),
425
- },
426
- ],
427
- isError: !result.success,
428
- };
356
+ return toolResult(formatMigrateResult(result), !result.success);
429
357
  }
430
358
  default:
431
359
  throw new Error(`Unknown tool: ${name}`);
@@ -436,13 +364,7 @@ const orgId = fullUser.organizationId;
436
364
  // ===========================================================================
437
365
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
438
366
  log('Listing prompts');
439
- const prompts = listPromptTemplates();
440
- return {
441
- prompts: prompts.map((p) => ({
442
- name: p.name,
443
- description: p.description,
444
- })),
445
- };
367
+ return { prompts: listPromptTemplates() };
446
368
  });
447
369
  server.setRequestHandler(GetPromptRequestSchema, async (request) => {
448
370
  const { name } = request.params;
@@ -3,9 +3,8 @@
3
3
  *
4
4
  * Wraps `velox make` commands for AI tool invocation.
5
5
  */
6
- import { spawn } from 'node:child_process';
7
6
  import { z } from 'zod';
8
- import { resolveVeloxCLI } from '../utils/cli.js';
7
+ import { spawnCLI } from '../utils/cli.js';
9
8
  import { findProjectRoot } from '../utils/project.js';
10
9
  // ============================================================================
11
10
  // Output Validation Schemas
@@ -59,69 +58,40 @@ export async function generate(options, serverProjectRoot) {
59
58
  };
60
59
  }
61
60
  const args = buildArgs(options);
62
- const resolved = resolveVeloxCLI(projectRoot, args);
63
- return new Promise((resolve) => {
64
- const child = spawn(resolved.command, resolved.args, {
65
- cwd: projectRoot,
66
- shell: resolved.isNpx, // Only use shell for npx fallback
67
- stdio: ['pipe', 'pipe', 'pipe'],
68
- });
69
- let stdout = '';
70
- let stderr = '';
71
- child.stdout?.on('data', (data) => {
72
- stdout += data.toString();
73
- });
74
- child.stderr?.on('data', (data) => {
75
- stderr += data.toString();
76
- });
77
- child.on('close', (code) => {
78
- if (code === 0) {
79
- // Try to parse and validate JSON output if requested
80
- if (options.json) {
81
- try {
82
- const jsonData = JSON.parse(stdout);
83
- const parsed = GenerateOutputSchema.safeParse(jsonData);
84
- if (parsed.success) {
85
- resolve({
86
- success: true,
87
- type: options.type,
88
- name: options.name,
89
- files: parsed.data.files,
90
- output: stdout,
91
- });
92
- return;
93
- }
94
- // Invalid JSON structure - fall through to plain output
95
- }
96
- catch {
97
- // JSON parse failed - fall through to plain output
98
- }
99
- }
100
- resolve({
61
+ const result = await spawnCLI(projectRoot, args);
62
+ if (!result.success) {
63
+ return {
64
+ success: false,
65
+ type: options.type,
66
+ name: options.name,
67
+ error: result.stderr || result.stdout || `Command failed with exit code ${result.code}`,
68
+ };
69
+ }
70
+ // Try to parse and validate JSON output if requested
71
+ if (options.json) {
72
+ try {
73
+ const jsonData = JSON.parse(result.stdout);
74
+ const parsed = GenerateOutputSchema.safeParse(jsonData);
75
+ if (parsed.success) {
76
+ return {
101
77
  success: true,
102
78
  type: options.type,
103
79
  name: options.name,
104
- output: stdout,
105
- });
80
+ files: parsed.data.files,
81
+ output: result.stdout,
82
+ };
106
83
  }
107
- else {
108
- resolve({
109
- success: false,
110
- type: options.type,
111
- name: options.name,
112
- error: stderr || stdout || `Command failed with exit code ${code}`,
113
- });
114
- }
115
- });
116
- child.on('error', (err) => {
117
- resolve({
118
- success: false,
119
- type: options.type,
120
- name: options.name,
121
- error: err.message,
122
- });
123
- });
124
- });
84
+ }
85
+ catch {
86
+ // JSON parse failed - fall through to plain output
87
+ }
88
+ }
89
+ return {
90
+ success: true,
91
+ type: options.type,
92
+ name: options.name,
93
+ output: result.stdout,
94
+ };
125
95
  }
126
96
  /**
127
97
  * Generate a procedure
@@ -3,9 +3,8 @@
3
3
  *
4
4
  * Wraps `velox migrate` commands for AI tool invocation.
5
5
  */
6
- import { spawn } from 'node:child_process';
7
6
  import { z } from 'zod';
8
- import { resolveVeloxCLI } from '../utils/cli.js';
7
+ import { spawnCLI } from '../utils/cli.js';
9
8
  import { findProjectRoot } from '../utils/project.js';
10
9
  // ============================================================================
11
10
  // Output Validation Schemas
@@ -58,65 +57,37 @@ export async function migrate(options, serverProjectRoot) {
58
57
  };
59
58
  }
60
59
  const args = buildArgs(options);
61
- const resolved = resolveVeloxCLI(projectRoot, args);
62
- return new Promise((resolve) => {
63
- const child = spawn(resolved.command, resolved.args, {
64
- cwd: projectRoot,
65
- shell: resolved.isNpx, // Only use shell for npx fallback
66
- stdio: ['pipe', 'pipe', 'pipe'],
67
- });
68
- let stdout = '';
69
- let stderr = '';
70
- child.stdout?.on('data', (data) => {
71
- stdout += data.toString();
72
- });
73
- child.stderr?.on('data', (data) => {
74
- stderr += data.toString();
75
- });
76
- child.on('close', (code) => {
77
- if (code === 0) {
78
- // Try to parse and validate JSON output if requested
79
- if (options.json) {
80
- try {
81
- const jsonData = JSON.parse(stdout);
82
- const parsed = MigrateOutputSchema.safeParse(jsonData);
83
- if (parsed.success) {
84
- resolve({
85
- success: true,
86
- action: options.action,
87
- migrations: parsed.data.migrations,
88
- output: stdout,
89
- });
90
- return;
91
- }
92
- // Invalid JSON structure - fall through to plain output
93
- }
94
- catch {
95
- // JSON parse failed - fall through to plain output
96
- }
97
- }
98
- resolve({
60
+ const result = await spawnCLI(projectRoot, args);
61
+ if (!result.success) {
62
+ return {
63
+ success: false,
64
+ action: options.action,
65
+ error: result.stderr || result.stdout || `Command failed with exit code ${result.code}`,
66
+ };
67
+ }
68
+ // Try to parse and validate JSON output if requested
69
+ if (options.json) {
70
+ try {
71
+ const jsonData = JSON.parse(result.stdout);
72
+ const parsed = MigrateOutputSchema.safeParse(jsonData);
73
+ if (parsed.success) {
74
+ return {
99
75
  success: true,
100
76
  action: options.action,
101
- output: stdout,
102
- });
77
+ migrations: parsed.data.migrations,
78
+ output: result.stdout,
79
+ };
103
80
  }
104
- else {
105
- resolve({
106
- success: false,
107
- action: options.action,
108
- error: stderr || stdout || `Command failed with exit code ${code}`,
109
- });
110
- }
111
- });
112
- child.on('error', (err) => {
113
- resolve({
114
- success: false,
115
- action: options.action,
116
- error: err.message,
117
- });
118
- });
119
- });
81
+ }
82
+ catch {
83
+ // JSON parse failed - fall through to plain output
84
+ }
85
+ }
86
+ return {
87
+ success: true,
88
+ action: options.action,
89
+ output: result.stdout,
90
+ };
120
91
  }
121
92
  /**
122
93
  * Get migration status
@@ -25,3 +25,19 @@ export interface ResolvedCLI {
25
25
  * 5. Fallback to npx @veloxts/cli (slowest, downloads if needed)
26
26
  */
27
27
  export declare function resolveVeloxCLI(projectRoot: string, args: string[]): ResolvedCLI;
28
+ /**
29
+ * Result from spawning a CLI command
30
+ */
31
+ export interface SpawnCLIResult {
32
+ success: boolean;
33
+ stdout: string;
34
+ stderr: string;
35
+ code: number | null;
36
+ }
37
+ /**
38
+ * Spawn a VeloxTS CLI command and collect its output.
39
+ *
40
+ * Resolves the CLI binary, spawns the process, and returns stdout/stderr.
41
+ * Never rejects - errors are returned in the result.
42
+ */
43
+ export declare function spawnCLI(projectRoot: string, args: string[]): Promise<SpawnCLIResult>;
package/dist/utils/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Utilities for finding and executing the VeloxTS CLI binary.
5
5
  */
6
+ import { spawn } from 'node:child_process';
6
7
  import { existsSync, readFileSync } from 'node:fs';
7
8
  import { join } from 'node:path';
8
9
  /**
@@ -77,3 +78,33 @@ export function resolveVeloxCLI(projectRoot, args) {
77
78
  isNpx: true,
78
79
  };
79
80
  }
81
+ /**
82
+ * Spawn a VeloxTS CLI command and collect its output.
83
+ *
84
+ * Resolves the CLI binary, spawns the process, and returns stdout/stderr.
85
+ * Never rejects - errors are returned in the result.
86
+ */
87
+ export function spawnCLI(projectRoot, args) {
88
+ const resolved = resolveVeloxCLI(projectRoot, args);
89
+ return new Promise((resolve) => {
90
+ const child = spawn(resolved.command, resolved.args, {
91
+ cwd: projectRoot,
92
+ shell: resolved.isNpx,
93
+ stdio: ['pipe', 'pipe', 'pipe'],
94
+ });
95
+ let stdout = '';
96
+ let stderr = '';
97
+ child.stdout?.on('data', (data) => {
98
+ stdout += data.toString();
99
+ });
100
+ child.stderr?.on('data', (data) => {
101
+ stderr += data.toString();
102
+ });
103
+ child.on('close', (code) => {
104
+ resolve({ success: code === 0, stdout, stderr, code });
105
+ });
106
+ child.on('error', (err) => {
107
+ resolve({ success: false, stdout, stderr: err.message, code: null });
108
+ });
109
+ });
110
+ }
@@ -99,34 +99,28 @@ export async function getProjectInfo(projectRoot) {
99
99
  }
100
100
  }
101
101
  /**
102
- * Get the procedures directory path for a project
102
+ * Resolve a source subdirectory, checking monorepo layout first, then single-package.
103
103
  */
104
- export function getProceduresPath(projectRoot) {
105
- // Check monorepo structure first
106
- const monorepoProcedures = join(projectRoot, 'apps', 'api', 'src', 'procedures');
107
- if (existsSync(monorepoProcedures)) {
108
- return monorepoProcedures;
104
+ function resolveSourcePath(projectRoot, subdir) {
105
+ const monorepoPath = join(projectRoot, 'apps', 'api', 'src', subdir);
106
+ if (existsSync(monorepoPath)) {
107
+ return monorepoPath;
109
108
  }
110
- // Check single package structure
111
- const singleProcedures = join(projectRoot, 'src', 'procedures');
112
- if (existsSync(singleProcedures)) {
113
- return singleProcedures;
109
+ const singlePath = join(projectRoot, 'src', subdir);
110
+ if (existsSync(singlePath)) {
111
+ return singlePath;
114
112
  }
115
113
  return null;
116
114
  }
115
+ /**
116
+ * Get the procedures directory path for a project
117
+ */
118
+ export function getProceduresPath(projectRoot) {
119
+ return resolveSourcePath(projectRoot, 'procedures');
120
+ }
117
121
  /**
118
122
  * Get the schemas directory path for a project
119
123
  */
120
124
  export function getSchemasPath(projectRoot) {
121
- // Check monorepo structure first
122
- const monorepoSchemas = join(projectRoot, 'apps', 'api', 'src', 'schemas');
123
- if (existsSync(monorepoSchemas)) {
124
- return monorepoSchemas;
125
- }
126
- // Check single package structure
127
- const singleSchemas = join(projectRoot, 'src', 'schemas');
128
- if (existsSync(singleSchemas)) {
129
- return singleSchemas;
130
- }
131
- return null;
125
+ return resolveSourcePath(projectRoot, 'schemas');
132
126
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/mcp",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
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",
@@ -22,17 +22,17 @@
22
22
  "CHANGELOG.md"
23
23
  ],
24
24
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "1.25.3",
25
+ "@modelcontextprotocol/sdk": "1.26.0",
26
26
  "typescript": "5.9.3",
27
- "@veloxts/cli": "0.7.0",
28
- "@veloxts/router": "0.7.0",
29
- "@veloxts/validation": "0.7.0"
27
+ "@veloxts/cli": "0.7.2",
28
+ "@veloxts/router": "0.7.2",
29
+ "@veloxts/validation": "0.7.2"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "zod": "^4.3.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@types/node": "25.1.0",
35
+ "@types/node": "25.2.3",
36
36
  "@vitest/coverage-v8": "4.0.18",
37
37
  "typescript": "5.9.3",
38
38
  "vitest": "4.0.18"