@vibescope/mcp-server 0.2.0 → 0.2.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.
Files changed (104) hide show
  1. package/README.md +60 -7
  2. package/dist/api-client.d.ts +251 -1
  3. package/dist/api-client.js +82 -3
  4. package/dist/handlers/blockers.js +9 -8
  5. package/dist/handlers/bodies-of-work.js +96 -63
  6. package/dist/handlers/connectors.d.ts +45 -0
  7. package/dist/handlers/connectors.js +183 -0
  8. package/dist/handlers/cost.d.ts +10 -0
  9. package/dist/handlers/cost.js +112 -50
  10. package/dist/handlers/decisions.js +32 -19
  11. package/dist/handlers/deployment.js +144 -122
  12. package/dist/handlers/discovery.d.ts +7 -0
  13. package/dist/handlers/discovery.js +96 -7
  14. package/dist/handlers/fallback.js +29 -23
  15. package/dist/handlers/file-checkouts.d.ts +20 -0
  16. package/dist/handlers/file-checkouts.js +133 -0
  17. package/dist/handlers/findings.d.ts +6 -0
  18. package/dist/handlers/findings.js +96 -40
  19. package/dist/handlers/git-issues.js +40 -36
  20. package/dist/handlers/ideas.js +49 -31
  21. package/dist/handlers/index.d.ts +3 -0
  22. package/dist/handlers/index.js +9 -0
  23. package/dist/handlers/milestones.js +39 -32
  24. package/dist/handlers/organizations.js +99 -91
  25. package/dist/handlers/progress.js +24 -13
  26. package/dist/handlers/project.js +68 -28
  27. package/dist/handlers/requests.js +18 -14
  28. package/dist/handlers/roles.d.ts +18 -0
  29. package/dist/handlers/roles.js +130 -0
  30. package/dist/handlers/session.js +58 -17
  31. package/dist/handlers/sprints.js +93 -81
  32. package/dist/handlers/tasks.d.ts +2 -0
  33. package/dist/handlers/tasks.js +189 -91
  34. package/dist/handlers/types.d.ts +64 -2
  35. package/dist/handlers/types.js +48 -1
  36. package/dist/handlers/validation.js +21 -17
  37. package/dist/index.js +7 -2716
  38. package/dist/token-tracking.d.ts +74 -0
  39. package/dist/token-tracking.js +122 -0
  40. package/dist/tools.js +685 -9
  41. package/dist/utils.d.ts +5 -0
  42. package/dist/utils.js +17 -0
  43. package/docs/TOOLS.md +2053 -0
  44. package/package.json +4 -1
  45. package/scripts/generate-docs.ts +212 -0
  46. package/src/api-client.test.ts +718 -0
  47. package/src/api-client.ts +320 -6
  48. package/src/handlers/__test-setup__.ts +16 -0
  49. package/src/handlers/blockers.test.ts +31 -19
  50. package/src/handlers/blockers.ts +9 -8
  51. package/src/handlers/bodies-of-work.test.ts +55 -32
  52. package/src/handlers/bodies-of-work.ts +115 -115
  53. package/src/handlers/connectors.test.ts +834 -0
  54. package/src/handlers/connectors.ts +229 -0
  55. package/src/handlers/cost.test.ts +34 -44
  56. package/src/handlers/cost.ts +136 -85
  57. package/src/handlers/decisions.test.ts +37 -27
  58. package/src/handlers/decisions.ts +35 -30
  59. package/src/handlers/deployment.ts +180 -208
  60. package/src/handlers/discovery.test.ts +4 -5
  61. package/src/handlers/discovery.ts +98 -8
  62. package/src/handlers/fallback.test.ts +26 -22
  63. package/src/handlers/fallback.ts +36 -33
  64. package/src/handlers/file-checkouts.test.ts +670 -0
  65. package/src/handlers/file-checkouts.ts +165 -0
  66. package/src/handlers/findings.test.ts +178 -19
  67. package/src/handlers/findings.ts +112 -74
  68. package/src/handlers/git-issues.test.ts +51 -43
  69. package/src/handlers/git-issues.ts +44 -84
  70. package/src/handlers/ideas.test.ts +28 -23
  71. package/src/handlers/ideas.ts +61 -59
  72. package/src/handlers/index.ts +9 -0
  73. package/src/handlers/milestones.test.ts +33 -28
  74. package/src/handlers/milestones.ts +52 -50
  75. package/src/handlers/organizations.test.ts +104 -83
  76. package/src/handlers/organizations.ts +117 -142
  77. package/src/handlers/progress.test.ts +20 -14
  78. package/src/handlers/progress.ts +26 -24
  79. package/src/handlers/project.test.ts +34 -27
  80. package/src/handlers/project.ts +95 -63
  81. package/src/handlers/requests.test.ts +27 -18
  82. package/src/handlers/requests.ts +21 -17
  83. package/src/handlers/roles.test.ts +303 -0
  84. package/src/handlers/roles.ts +208 -0
  85. package/src/handlers/session.test.ts +47 -0
  86. package/src/handlers/session.ts +71 -26
  87. package/src/handlers/sprints.test.ts +71 -50
  88. package/src/handlers/sprints.ts +113 -146
  89. package/src/handlers/tasks.test.ts +77 -15
  90. package/src/handlers/tasks.ts +231 -156
  91. package/src/handlers/tool-categories.test.ts +66 -0
  92. package/src/handlers/types.ts +81 -2
  93. package/src/handlers/validation.test.ts +78 -45
  94. package/src/handlers/validation.ts +23 -25
  95. package/src/index.ts +12 -2732
  96. package/src/token-tracking.test.ts +453 -0
  97. package/src/token-tracking.ts +164 -0
  98. package/src/tools.ts +685 -9
  99. package/src/utils.test.ts +2 -2
  100. package/src/utils.ts +17 -0
  101. package/dist/config/tool-categories.d.ts +0 -31
  102. package/dist/config/tool-categories.js +0 -253
  103. package/dist/knowledge.d.ts +0 -6
  104. package/dist/knowledge.js +0 -218
@@ -17,34 +17,134 @@
17
17
  import type { Handler, HandlerRegistry } from './types.js';
18
18
  import {
19
19
  ValidationError,
20
- validateRequired,
21
- validateUUID,
22
- validateEnvironment,
20
+ parseArgs,
21
+ uuidValidator,
22
+ createEnumValidator,
23
23
  } from '../validators.js';
24
24
  import { getApiClient } from '../api-client.js';
25
25
 
26
- export const requestDeployment: Handler = async (args, ctx) => {
27
- const { project_id, environment = 'production', version_bump = 'patch', notes, git_ref } = args as {
28
- project_id: string;
29
- environment?: string;
30
- version_bump?: 'patch' | 'minor' | 'major';
31
- notes?: string;
32
- git_ref?: string;
33
- };
26
+ const ENVIRONMENTS = ['development', 'staging', 'production'] as const;
27
+ const VERSION_BUMPS = ['patch', 'minor', 'major'] as const;
28
+ const REQUIREMENT_TYPES = ['migration', 'env_var', 'config', 'manual', 'breaking_change', 'agent_task'] as const;
29
+ const REQUIREMENT_STAGES = ['preparation', 'deployment', 'verification'] as const;
30
+ const REQUIREMENT_STATUSES = ['pending', 'completed', 'converted_to_task', 'all'] as const;
31
+ const SCHEDULE_TYPES = ['once', 'hourly', 'daily', 'weekly', 'monthly'] as const;
34
32
 
35
- const { session } = ctx;
33
+ type Environment = typeof ENVIRONMENTS[number];
34
+ type VersionBump = typeof VERSION_BUMPS[number];
35
+ type RequirementType = typeof REQUIREMENT_TYPES[number];
36
+ type RequirementStage = typeof REQUIREMENT_STAGES[number];
37
+ type RequirementStatus = typeof REQUIREMENT_STATUSES[number];
38
+ type ScheduleType = typeof SCHEDULE_TYPES[number];
39
+
40
+ // ============================================================================
41
+ // Argument Schemas
42
+ // ============================================================================
36
43
 
37
- validateRequired(project_id, 'project_id');
38
- validateUUID(project_id, 'project_id');
39
- validateEnvironment(environment);
44
+ const requestDeploymentSchema = {
45
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
46
+ environment: { type: 'string' as const, default: 'production', validate: createEnumValidator(ENVIRONMENTS) },
47
+ version_bump: { type: 'string' as const, default: 'patch', validate: createEnumValidator(VERSION_BUMPS) },
48
+ notes: { type: 'string' as const },
49
+ git_ref: { type: 'string' as const },
50
+ };
40
51
 
41
- if (version_bump && !['patch', 'minor', 'major'].includes(version_bump)) {
42
- throw new ValidationError('Invalid version_bump value', {
43
- field: 'version_bump',
44
- validValues: ['patch', 'minor', 'major'],
45
- hint: 'Must be one of: patch, minor, major',
46
- });
47
- }
52
+ const claimDeploymentValidationSchema = {
53
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
54
+ };
55
+
56
+ const reportValidationSchema = {
57
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
58
+ build_passed: { type: 'boolean' as const, required: true as const },
59
+ tests_passed: { type: 'boolean' as const },
60
+ error_message: { type: 'string' as const },
61
+ };
62
+
63
+ const checkDeploymentStatusSchema = {
64
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
65
+ };
66
+
67
+ const startDeploymentSchema = {
68
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
69
+ };
70
+
71
+ const completeDeploymentSchema = {
72
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
73
+ success: { type: 'boolean' as const, required: true as const },
74
+ summary: { type: 'string' as const },
75
+ };
76
+
77
+ const cancelDeploymentSchema = {
78
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
79
+ reason: { type: 'string' as const },
80
+ };
81
+
82
+ const addDeploymentRequirementSchema = {
83
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
84
+ type: { type: 'string' as const, required: true as const, validate: createEnumValidator(REQUIREMENT_TYPES) },
85
+ title: { type: 'string' as const, required: true as const },
86
+ description: { type: 'string' as const },
87
+ file_path: { type: 'string' as const },
88
+ stage: { type: 'string' as const, default: 'preparation', validate: createEnumValidator(REQUIREMENT_STAGES) },
89
+ blocking: { type: 'boolean' as const, default: false },
90
+ recurring: { type: 'boolean' as const, default: false },
91
+ };
92
+
93
+ const completeDeploymentRequirementSchema = {
94
+ requirement_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
95
+ };
96
+
97
+ const getDeploymentRequirementsSchema = {
98
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
99
+ status: { type: 'string' as const, default: 'pending', validate: createEnumValidator(REQUIREMENT_STATUSES) },
100
+ stage: { type: 'string' as const, validate: createEnumValidator([...REQUIREMENT_STAGES, 'all'] as const) },
101
+ };
102
+
103
+ const scheduleDeploymentSchema = {
104
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
105
+ environment: { type: 'string' as const, default: 'production', validate: createEnumValidator(ENVIRONMENTS) },
106
+ version_bump: { type: 'string' as const, default: 'patch', validate: createEnumValidator(VERSION_BUMPS) },
107
+ schedule_type: { type: 'string' as const, default: 'once', validate: createEnumValidator(SCHEDULE_TYPES) },
108
+ scheduled_at: { type: 'string' as const, required: true as const },
109
+ auto_trigger: { type: 'boolean' as const, default: true },
110
+ hours_interval: { type: 'number' as const, default: 1 },
111
+ notes: { type: 'string' as const },
112
+ git_ref: { type: 'string' as const },
113
+ };
114
+
115
+ const getScheduledDeploymentsSchema = {
116
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
117
+ include_disabled: { type: 'boolean' as const, default: false },
118
+ };
119
+
120
+ const updateScheduledDeploymentSchema = {
121
+ schedule_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
122
+ environment: { type: 'string' as const, validate: createEnumValidator(ENVIRONMENTS) },
123
+ version_bump: { type: 'string' as const, validate: createEnumValidator(VERSION_BUMPS) },
124
+ schedule_type: { type: 'string' as const, validate: createEnumValidator(SCHEDULE_TYPES) },
125
+ scheduled_at: { type: 'string' as const },
126
+ auto_trigger: { type: 'boolean' as const },
127
+ hours_interval: { type: 'number' as const },
128
+ enabled: { type: 'boolean' as const },
129
+ notes: { type: 'string' as const },
130
+ git_ref: { type: 'string' as const },
131
+ };
132
+
133
+ const deleteScheduledDeploymentSchema = {
134
+ schedule_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
135
+ };
136
+
137
+ const triggerScheduledDeploymentSchema = {
138
+ schedule_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
139
+ };
140
+
141
+ const checkDueDeploymentsSchema = {
142
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
143
+ };
144
+
145
+ export const requestDeployment: Handler = async (args, ctx) => {
146
+ const { project_id, environment, version_bump, notes, git_ref } = parseArgs(args, requestDeploymentSchema);
147
+ const { session } = ctx;
48
148
 
49
149
  const apiClient = getApiClient();
50
150
  const response = await apiClient.requestDeployment(project_id, {
@@ -55,19 +155,16 @@ export const requestDeployment: Handler = async (args, ctx) => {
55
155
  });
56
156
 
57
157
  if (!response.ok) {
58
- throw new Error(response.error || 'Failed to request deployment');
158
+ return { result: { error: response.error || 'Failed to request deployment' }, isError: true };
59
159
  }
60
160
 
61
161
  return { result: response.data };
62
162
  };
63
163
 
64
164
  export const claimDeploymentValidation: Handler = async (args, ctx) => {
65
- const { project_id } = args as { project_id: string };
165
+ const { project_id } = parseArgs(args, claimDeploymentValidationSchema);
66
166
  const { session } = ctx;
67
167
 
68
- validateRequired(project_id, 'project_id');
69
- validateUUID(project_id, 'project_id');
70
-
71
168
  const apiClient = getApiClient();
72
169
  const response = await apiClient.claimDeploymentValidation(
73
170
  project_id,
@@ -75,31 +172,16 @@ export const claimDeploymentValidation: Handler = async (args, ctx) => {
75
172
  );
76
173
 
77
174
  if (!response.ok) {
78
- throw new Error(response.error || 'Failed to claim deployment validation');
175
+ return { result: { error: response.error || 'Failed to claim deployment validation' }, isError: true };
79
176
  }
80
177
 
81
178
  return { result: response.data };
82
179
  };
83
180
 
84
181
  export const reportValidation: Handler = async (args, ctx) => {
85
- const { project_id, build_passed, tests_passed, error_message } = args as {
86
- project_id: string;
87
- build_passed: boolean;
88
- tests_passed?: boolean;
89
- error_message?: string;
90
- };
91
-
182
+ const { project_id, build_passed, tests_passed, error_message } = parseArgs(args, reportValidationSchema);
92
183
  const { session } = ctx;
93
184
 
94
- validateRequired(project_id, 'project_id');
95
- validateUUID(project_id, 'project_id');
96
- if (build_passed === undefined) {
97
- throw new ValidationError('build_passed is required', {
98
- field: 'build_passed',
99
- hint: 'Set to true if the build succeeded, false otherwise',
100
- });
101
- }
102
-
103
185
  const apiClient = getApiClient();
104
186
  const response = await apiClient.reportValidation(project_id, {
105
187
  build_passed,
@@ -108,35 +190,29 @@ export const reportValidation: Handler = async (args, ctx) => {
108
190
  });
109
191
 
110
192
  if (!response.ok) {
111
- throw new Error(response.error || 'Failed to report validation');
193
+ return { result: { error: response.error || 'Failed to report validation' }, isError: true };
112
194
  }
113
195
 
114
196
  return { result: response.data };
115
197
  };
116
198
 
117
199
  export const checkDeploymentStatus: Handler = async (args, ctx) => {
118
- const { project_id } = args as { project_id: string };
119
-
120
- validateRequired(project_id, 'project_id');
121
- validateUUID(project_id, 'project_id');
200
+ const { project_id } = parseArgs(args, checkDeploymentStatusSchema);
122
201
 
123
202
  const apiClient = getApiClient();
124
203
  const response = await apiClient.checkDeploymentStatus(project_id);
125
204
 
126
205
  if (!response.ok) {
127
- throw new Error(response.error || 'Failed to check deployment status');
206
+ return { result: { error: response.error || 'Failed to check deployment status' }, isError: true };
128
207
  }
129
208
 
130
209
  return { result: response.data };
131
210
  };
132
211
 
133
212
  export const startDeployment: Handler = async (args, ctx) => {
134
- const { project_id } = args as { project_id: string };
213
+ const { project_id } = parseArgs(args, startDeploymentSchema);
135
214
  const { session } = ctx;
136
215
 
137
- validateRequired(project_id, 'project_id');
138
- validateUUID(project_id, 'project_id');
139
-
140
216
  const apiClient = getApiClient();
141
217
  const response = await apiClient.startDeployment(
142
218
  project_id,
@@ -144,30 +220,16 @@ export const startDeployment: Handler = async (args, ctx) => {
144
220
  );
145
221
 
146
222
  if (!response.ok) {
147
- throw new Error(response.error || 'Failed to start deployment');
223
+ return { result: { error: response.error || 'Failed to start deployment' }, isError: true };
148
224
  }
149
225
 
150
226
  return { result: response.data };
151
227
  };
152
228
 
153
229
  export const completeDeployment: Handler = async (args, ctx) => {
154
- const { project_id, success, summary } = args as {
155
- project_id: string;
156
- success: boolean;
157
- summary?: string;
158
- };
159
-
230
+ const { project_id, success, summary } = parseArgs(args, completeDeploymentSchema);
160
231
  const { session } = ctx;
161
232
 
162
- validateRequired(project_id, 'project_id');
163
- validateUUID(project_id, 'project_id');
164
- if (success === undefined) {
165
- throw new ValidationError('success is required', {
166
- field: 'success',
167
- hint: 'Set to true if deployment succeeded, false otherwise',
168
- });
169
- }
170
-
171
233
  const apiClient = getApiClient();
172
234
  const response = await apiClient.completeDeployment(project_id, {
173
235
  success,
@@ -175,54 +237,27 @@ export const completeDeployment: Handler = async (args, ctx) => {
175
237
  });
176
238
 
177
239
  if (!response.ok) {
178
- throw new Error(response.error || 'Failed to complete deployment');
240
+ return { result: { error: response.error || 'Failed to complete deployment' }, isError: true };
179
241
  }
180
242
 
181
243
  return { result: response.data };
182
244
  };
183
245
 
184
246
  export const cancelDeployment: Handler = async (args, ctx) => {
185
- const { project_id, reason } = args as { project_id: string; reason?: string };
186
-
187
- validateRequired(project_id, 'project_id');
188
- validateUUID(project_id, 'project_id');
247
+ const { project_id, reason } = parseArgs(args, cancelDeploymentSchema);
189
248
 
190
249
  const apiClient = getApiClient();
191
250
  const response = await apiClient.cancelDeployment(project_id, reason);
192
251
 
193
252
  if (!response.ok) {
194
- throw new Error(response.error || 'Failed to cancel deployment');
253
+ return { result: { error: response.error || 'Failed to cancel deployment' }, isError: true };
195
254
  }
196
255
 
197
256
  return { result: response.data };
198
257
  };
199
258
 
200
259
  export const addDeploymentRequirement: Handler = async (args, ctx) => {
201
- const { project_id, type, title, description, file_path, stage = 'preparation', blocking = false, recurring = false } = args as {
202
- project_id: string;
203
- type: string;
204
- title: string;
205
- description?: string;
206
- file_path?: string;
207
- stage?: string;
208
- blocking?: boolean;
209
- recurring?: boolean;
210
- };
211
-
212
- validateRequired(project_id, 'project_id');
213
- validateUUID(project_id, 'project_id');
214
- validateRequired(type, 'type');
215
- validateRequired(title, 'title');
216
-
217
- const validTypes = ['migration', 'env_var', 'config', 'manual', 'breaking_change', 'agent_task'];
218
- if (!validTypes.includes(type)) {
219
- throw new ValidationError(`type must be one of: ${validTypes.join(', ')}`);
220
- }
221
-
222
- const validStages = ['preparation', 'deployment', 'verification'];
223
- if (!validStages.includes(stage)) {
224
- throw new ValidationError(`stage must be one of: ${validStages.join(', ')}`);
225
- }
260
+ const { project_id, type, title, description, file_path, stage, blocking, recurring } = parseArgs(args, addDeploymentRequirementSchema);
226
261
 
227
262
  const apiClient = getApiClient();
228
263
  const response = await apiClient.addDeploymentRequirement(project_id, {
@@ -236,37 +271,27 @@ export const addDeploymentRequirement: Handler = async (args, ctx) => {
236
271
  });
237
272
 
238
273
  if (!response.ok) {
239
- throw new Error(response.error || 'Failed to add deployment requirement');
274
+ return { result: { error: response.error || 'Failed to add deployment requirement' }, isError: true };
240
275
  }
241
276
 
242
277
  return { result: response.data };
243
278
  };
244
279
 
245
280
  export const completeDeploymentRequirement: Handler = async (args, ctx) => {
246
- const { requirement_id } = args as { requirement_id: string };
247
-
248
- validateRequired(requirement_id, 'requirement_id');
249
- validateUUID(requirement_id, 'requirement_id');
281
+ const { requirement_id } = parseArgs(args, completeDeploymentRequirementSchema);
250
282
 
251
283
  const apiClient = getApiClient();
252
284
  const response = await apiClient.completeDeploymentRequirement(requirement_id);
253
285
 
254
286
  if (!response.ok) {
255
- throw new Error(response.error || 'Failed to complete deployment requirement');
287
+ return { result: { error: response.error || 'Failed to complete deployment requirement' }, isError: true };
256
288
  }
257
289
 
258
290
  return { result: response.data };
259
291
  };
260
292
 
261
293
  export const getDeploymentRequirements: Handler = async (args, ctx) => {
262
- const { project_id, status = 'pending', stage } = args as {
263
- project_id: string;
264
- status?: string;
265
- stage?: string;
266
- };
267
-
268
- validateRequired(project_id, 'project_id');
269
- validateUUID(project_id, 'project_id');
294
+ const { project_id, status, stage } = parseArgs(args, getDeploymentRequirementsSchema);
270
295
 
271
296
  const apiClient = getApiClient();
272
297
  const response = await apiClient.getDeploymentRequirements(project_id, {
@@ -275,7 +300,7 @@ export const getDeploymentRequirements: Handler = async (args, ctx) => {
275
300
  });
276
301
 
277
302
  if (!response.ok) {
278
- throw new Error(response.error || 'Failed to get deployment requirements');
303
+ return { result: { error: response.error || 'Failed to get deployment requirements' }, isError: true };
279
304
  }
280
305
 
281
306
  return { result: response.data };
@@ -288,42 +313,15 @@ export const getDeploymentRequirements: Handler = async (args, ctx) => {
288
313
  export const scheduleDeployment: Handler = async (args, ctx) => {
289
314
  const {
290
315
  project_id,
291
- environment = 'production',
292
- version_bump = 'patch',
293
- schedule_type = 'once',
316
+ environment,
317
+ version_bump,
318
+ schedule_type,
294
319
  scheduled_at,
295
- auto_trigger = true,
320
+ auto_trigger,
321
+ hours_interval,
296
322
  notes,
297
323
  git_ref,
298
- } = args as {
299
- project_id: string;
300
- environment?: string;
301
- version_bump?: string;
302
- schedule_type?: string;
303
- scheduled_at: string;
304
- auto_trigger?: boolean;
305
- notes?: string;
306
- git_ref?: string;
307
- };
308
-
309
- validateRequired(project_id, 'project_id');
310
- validateUUID(project_id, 'project_id');
311
- validateRequired(scheduled_at, 'scheduled_at');
312
- validateEnvironment(environment);
313
-
314
- if (!['patch', 'minor', 'major'].includes(version_bump)) {
315
- throw new ValidationError('Invalid version_bump value', {
316
- field: 'version_bump',
317
- validValues: ['patch', 'minor', 'major'],
318
- });
319
- }
320
-
321
- if (!['once', 'daily', 'weekly', 'monthly'].includes(schedule_type)) {
322
- throw new ValidationError('Invalid schedule_type value', {
323
- field: 'schedule_type',
324
- validValues: ['once', 'daily', 'weekly', 'monthly'],
325
- });
326
- }
324
+ } = parseArgs(args, scheduleDeploymentSchema);
327
325
 
328
326
  // Parse and validate scheduled_at
329
327
  const scheduledDate = new Date(scheduled_at);
@@ -340,38 +338,41 @@ export const scheduleDeployment: Handler = async (args, ctx) => {
340
338
  });
341
339
  }
342
340
 
341
+ // Validate hours_interval for hourly schedule type (default is 1)
342
+ const hoursInterval = hours_interval ?? 1;
343
+ if (schedule_type === 'hourly' && (hoursInterval < 1 || hoursInterval > 24)) {
344
+ throw new ValidationError('hours_interval must be between 1 and 24', {
345
+ field: 'hours_interval',
346
+ });
347
+ }
348
+
343
349
  const apiClient = getApiClient();
344
350
  const response = await apiClient.scheduleDeployment(project_id, {
345
351
  environment: environment as 'development' | 'staging' | 'production',
346
352
  version_bump: version_bump as 'patch' | 'minor' | 'major',
347
- schedule_type: schedule_type as 'once' | 'daily' | 'weekly' | 'monthly',
353
+ schedule_type: schedule_type as 'once' | 'hourly' | 'daily' | 'weekly' | 'monthly',
348
354
  scheduled_at: scheduledDate.toISOString(),
349
355
  auto_trigger,
356
+ hours_interval: hoursInterval,
350
357
  notes,
351
358
  git_ref
352
359
  });
353
360
 
354
361
  if (!response.ok) {
355
- throw new Error(response.error || 'Failed to schedule deployment');
362
+ return { result: { error: response.error || 'Failed to schedule deployment' }, isError: true };
356
363
  }
357
364
 
358
365
  return { result: response.data };
359
366
  };
360
367
 
361
368
  export const getScheduledDeployments: Handler = async (args, ctx) => {
362
- const { project_id, include_disabled = false } = args as {
363
- project_id: string;
364
- include_disabled?: boolean;
365
- };
366
-
367
- validateRequired(project_id, 'project_id');
368
- validateUUID(project_id, 'project_id');
369
+ const { project_id, include_disabled } = parseArgs(args, getScheduledDeploymentsSchema);
369
370
 
370
371
  const apiClient = getApiClient();
371
372
  const response = await apiClient.getScheduledDeployments(project_id, include_disabled);
372
373
 
373
374
  if (!response.ok) {
374
- throw new Error(response.error || 'Failed to get scheduled deployments');
375
+ return { result: { error: response.error || 'Failed to get scheduled deployments' }, isError: true };
375
376
  }
376
377
 
377
378
  return { result: response.data };
@@ -385,44 +386,17 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
385
386
  schedule_type,
386
387
  scheduled_at,
387
388
  auto_trigger,
389
+ hours_interval,
388
390
  enabled,
389
391
  notes,
390
392
  git_ref,
391
- } = args as {
392
- schedule_id: string;
393
- environment?: string;
394
- version_bump?: string;
395
- schedule_type?: string;
396
- scheduled_at?: string;
397
- auto_trigger?: boolean;
398
- enabled?: boolean;
399
- notes?: string;
400
- git_ref?: string;
401
- };
402
-
403
- validateRequired(schedule_id, 'schedule_id');
404
- validateUUID(schedule_id, 'schedule_id');
393
+ } = parseArgs(args, updateScheduledDeploymentSchema);
405
394
 
406
395
  const updates: Record<string, unknown> = {};
407
396
 
408
- if (environment !== undefined) {
409
- validateEnvironment(environment);
410
- updates.environment = environment;
411
- }
412
-
413
- if (version_bump !== undefined) {
414
- if (!['patch', 'minor', 'major'].includes(version_bump)) {
415
- throw new ValidationError('Invalid version_bump value');
416
- }
417
- updates.version_bump = version_bump;
418
- }
419
-
420
- if (schedule_type !== undefined) {
421
- if (!['once', 'daily', 'weekly', 'monthly'].includes(schedule_type)) {
422
- throw new ValidationError('Invalid schedule_type value');
423
- }
424
- updates.schedule_type = schedule_type;
425
- }
397
+ if (environment !== undefined) updates.environment = environment;
398
+ if (version_bump !== undefined) updates.version_bump = version_bump;
399
+ if (schedule_type !== undefined) updates.schedule_type = schedule_type;
426
400
 
427
401
  if (scheduled_at !== undefined) {
428
402
  const scheduledDate = new Date(scheduled_at);
@@ -433,6 +407,12 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
433
407
  }
434
408
 
435
409
  if (auto_trigger !== undefined) updates.auto_trigger = auto_trigger;
410
+ if (hours_interval !== undefined) {
411
+ if (hours_interval < 1 || hours_interval > 24) {
412
+ throw new ValidationError('hours_interval must be between 1 and 24');
413
+ }
414
+ updates.hours_interval = hours_interval;
415
+ }
436
416
  if (enabled !== undefined) updates.enabled = enabled;
437
417
  if (notes !== undefined) updates.notes = notes;
438
418
  if (git_ref !== undefined) updates.git_ref = git_ref;
@@ -445,44 +425,39 @@ export const updateScheduledDeployment: Handler = async (args, ctx) => {
445
425
  const response = await apiClient.updateScheduledDeployment(schedule_id, updates as {
446
426
  environment?: 'development' | 'staging' | 'production';
447
427
  version_bump?: 'patch' | 'minor' | 'major';
448
- schedule_type?: 'once' | 'daily' | 'weekly' | 'monthly';
428
+ schedule_type?: 'once' | 'hourly' | 'daily' | 'weekly' | 'monthly';
449
429
  scheduled_at?: string;
450
430
  auto_trigger?: boolean;
431
+ hours_interval?: number;
451
432
  enabled?: boolean;
452
433
  notes?: string;
453
434
  git_ref?: string;
454
435
  });
455
436
 
456
437
  if (!response.ok) {
457
- throw new Error(response.error || 'Failed to update scheduled deployment');
438
+ return { result: { error: response.error || 'Failed to update scheduled deployment' }, isError: true };
458
439
  }
459
440
 
460
441
  return { result: response.data };
461
442
  };
462
443
 
463
444
  export const deleteScheduledDeployment: Handler = async (args, ctx) => {
464
- const { schedule_id } = args as { schedule_id: string };
465
-
466
- validateRequired(schedule_id, 'schedule_id');
467
- validateUUID(schedule_id, 'schedule_id');
445
+ const { schedule_id } = parseArgs(args, deleteScheduledDeploymentSchema);
468
446
 
469
447
  const apiClient = getApiClient();
470
448
  const response = await apiClient.deleteScheduledDeployment(schedule_id);
471
449
 
472
450
  if (!response.ok) {
473
- throw new Error(response.error || 'Failed to delete scheduled deployment');
451
+ return { result: { error: response.error || 'Failed to delete scheduled deployment' }, isError: true };
474
452
  }
475
453
 
476
454
  return { result: response.data };
477
455
  };
478
456
 
479
457
  export const triggerScheduledDeployment: Handler = async (args, ctx) => {
480
- const { schedule_id } = args as { schedule_id: string };
458
+ const { schedule_id } = parseArgs(args, triggerScheduledDeploymentSchema);
481
459
  const { session } = ctx;
482
460
 
483
- validateRequired(schedule_id, 'schedule_id');
484
- validateUUID(schedule_id, 'schedule_id');
485
-
486
461
  const apiClient = getApiClient();
487
462
  const response = await apiClient.triggerScheduledDeployment(
488
463
  schedule_id,
@@ -490,23 +465,20 @@ export const triggerScheduledDeployment: Handler = async (args, ctx) => {
490
465
  );
491
466
 
492
467
  if (!response.ok) {
493
- throw new Error(response.error || 'Failed to trigger scheduled deployment');
468
+ return { result: { error: response.error || 'Failed to trigger scheduled deployment' }, isError: true };
494
469
  }
495
470
 
496
471
  return { result: response.data };
497
472
  };
498
473
 
499
474
  export const checkDueDeployments: Handler = async (args, ctx) => {
500
- const { project_id } = args as { project_id: string };
501
-
502
- validateRequired(project_id, 'project_id');
503
- validateUUID(project_id, 'project_id');
475
+ const { project_id } = parseArgs(args, checkDueDeploymentsSchema);
504
476
 
505
477
  const apiClient = getApiClient();
506
478
  const response = await apiClient.checkDueDeployments(project_id);
507
479
 
508
480
  if (!response.ok) {
509
- throw new Error(response.error || 'Failed to check due deployments');
481
+ return { result: { error: response.error || 'Failed to check due deployments' }, isError: true };
510
482
  }
511
483
 
512
484
  return { result: response.data };
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { discoverTools, getToolInfo } from './discovery.js';
3
3
  import { createMockContext } from './__test-utils__.js';
4
+ import { ValidationError } from '../validators.js';
4
5
 
5
6
  // ============================================================================
6
7
  // discoverTools Tests
@@ -113,13 +114,11 @@ describe('discoverTools', () => {
113
114
  // ============================================================================
114
115
 
115
116
  describe('getToolInfo', () => {
116
- it('should return error for missing tool_name', async () => {
117
+ it('should throw ValidationError for missing tool_name', async () => {
117
118
  const ctx = createMockContext();
118
119
 
119
- const result = await getToolInfo({}, ctx);
120
- const res = result.result as { error: string };
121
-
122
- expect(res.error).toBe('tool_name is required');
120
+ await expect(getToolInfo({}, ctx)).rejects.toThrow(ValidationError);
121
+ await expect(getToolInfo({}, ctx)).rejects.toThrow('Missing required field: tool_name');
123
122
  });
124
123
 
125
124
  it('should return error for unknown tool', async () => {