@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
@@ -74,14 +74,93 @@ export interface HandlerContext {
74
74
  }
75
75
 
76
76
  /**
77
- * Result returned by handlers
77
+ * Success result with typed data
78
78
  */
79
- export interface HandlerResult {
79
+ export interface SuccessResult<T = unknown> {
80
+ result: T;
81
+ content?: Array<{ type: string; text: string }>;
82
+ isError?: false;
83
+ }
84
+
85
+ /**
86
+ * Error result with error information
87
+ */
88
+ export interface ErrorResult {
89
+ result: { error: string; [key: string]: unknown };
90
+ content?: Array<{ type: string; text: string }>;
91
+ isError: true;
92
+ }
93
+
94
+ /**
95
+ * Result returned by handlers - discriminated union for type safety
96
+ * Use the helper functions success() and error() to create properly typed results.
97
+ */
98
+ export type HandlerResult<T = unknown> = SuccessResult<T> | ErrorResult;
99
+
100
+ /**
101
+ * Legacy HandlerResult interface for backward compatibility
102
+ * @deprecated Use HandlerResult<T> discriminated union instead
103
+ */
104
+ export interface LegacyHandlerResult {
80
105
  result?: unknown;
81
106
  content?: Array<{ type: string; text: string }>;
82
107
  isError?: boolean;
83
108
  }
84
109
 
110
+ // ============================================================================
111
+ // Result Factory Functions - use these for type-safe handler results
112
+ // ============================================================================
113
+
114
+ /**
115
+ * Create a success result with typed data
116
+ * @example
117
+ * return success({ tasks: data, total_count: count });
118
+ */
119
+ export function success<T>(data: T): SuccessResult<T> {
120
+ return { result: data };
121
+ }
122
+
123
+ /**
124
+ * Create an error result
125
+ * @example
126
+ * return error('Task not found');
127
+ * return error('Validation failed', { field: 'title', reason: 'too long' });
128
+ */
129
+ export function error(message: string, details?: Record<string, unknown>): ErrorResult {
130
+ return {
131
+ result: { error: message, ...details },
132
+ isError: true
133
+ };
134
+ }
135
+
136
+ // ============================================================================
137
+ // Type Predicates - use for runtime type narrowing
138
+ // ============================================================================
139
+
140
+ /**
141
+ * Check if a handler result is a success (not an error)
142
+ * @example
143
+ * const result = await handler(args, ctx);
144
+ * if (isSuccess(result)) {
145
+ * console.log(result.result); // typed as T
146
+ * }
147
+ */
148
+ export function isSuccess<T>(result: HandlerResult<T>): result is SuccessResult<T> {
149
+ return !result.isError;
150
+ }
151
+
152
+ /**
153
+ * Check if a handler result is an error
154
+ * @example
155
+ * const result = await handler(args, ctx);
156
+ * if (isError(result)) {
157
+ * console.log(result.result.error); // string
158
+ * }
159
+ */
160
+ export function isError(result: HandlerResult<unknown>): result is ErrorResult {
161
+ return result.isError === true;
162
+ }
163
+
85
164
  /**
86
165
  * Handler function type
87
166
  */
@@ -116,16 +116,19 @@ describe('getTasksAwaitingValidation', () => {
116
116
  expect(mockApiClient.getTasksAwaitingValidation).toHaveBeenCalledWith(VALID_UUID);
117
117
  });
118
118
 
119
- it('should throw error when API call fails', async () => {
119
+ it('should return error when API call fails', async () => {
120
120
  mockApiClient.getTasksAwaitingValidation.mockResolvedValue({
121
121
  ok: false,
122
122
  error: 'Failed to fetch tasks awaiting validation',
123
123
  });
124
124
  const ctx = createMockContext();
125
125
 
126
- await expect(
127
- getTasksAwaitingValidation({ project_id: VALID_UUID }, ctx)
128
- ).rejects.toThrow('Failed to fetch tasks awaiting validation');
126
+ const result = await getTasksAwaitingValidation({ project_id: VALID_UUID }, ctx);
127
+
128
+ expect(result.isError).toBe(true);
129
+ expect(result.result).toMatchObject({
130
+ error: 'Failed to fetch tasks awaiting validation',
131
+ });
129
132
  });
130
133
  });
131
134
 
@@ -150,52 +153,64 @@ describe('claimValidation', () => {
150
153
  ).rejects.toThrow(ValidationError);
151
154
  });
152
155
 
153
- it('should throw error when task not found', async () => {
156
+ it('should return error when task not found', async () => {
154
157
  mockApiClient.claimValidation.mockResolvedValue({
155
158
  ok: false,
156
159
  error: 'Task not found',
157
160
  });
158
161
  const ctx = createMockContext();
159
162
 
160
- await expect(
161
- claimValidation({ task_id: VALID_UUID }, ctx)
162
- ).rejects.toThrow('Task not found');
163
+ const result = await claimValidation({ task_id: VALID_UUID }, ctx);
164
+
165
+ expect(result.isError).toBe(true);
166
+ expect(result.result).toMatchObject({
167
+ error: 'Task not found',
168
+ });
163
169
  });
164
170
 
165
- it('should throw error when task is not completed', async () => {
171
+ it('should return error when task is not completed', async () => {
166
172
  mockApiClient.claimValidation.mockResolvedValue({
167
173
  ok: false,
168
174
  error: 'Can only claim completed tasks for review',
169
175
  });
170
176
  const ctx = createMockContext();
171
177
 
172
- await expect(
173
- claimValidation({ task_id: VALID_UUID }, ctx)
174
- ).rejects.toThrow('Can only claim completed tasks for review');
178
+ const result = await claimValidation({ task_id: VALID_UUID }, ctx);
179
+
180
+ expect(result.isError).toBe(true);
181
+ expect(result.result).toMatchObject({
182
+ error: 'Can only claim completed tasks for review',
183
+ });
175
184
  });
176
185
 
177
- it('should throw error when task is already validated', async () => {
186
+ it('should return error when task is already validated', async () => {
178
187
  mockApiClient.claimValidation.mockResolvedValue({
179
188
  ok: false,
180
189
  error: 'Task has already been validated',
181
190
  });
182
191
  const ctx = createMockContext();
183
192
 
184
- await expect(
185
- claimValidation({ task_id: VALID_UUID }, ctx)
186
- ).rejects.toThrow('Task has already been validated');
193
+ const result = await claimValidation({ task_id: VALID_UUID }, ctx);
194
+
195
+ expect(result.isError).toBe(true);
196
+ expect(result.result).toMatchObject({
197
+ error: 'Task has already been validated',
198
+ });
187
199
  });
188
200
 
189
- it('should throw error when task is being reviewed by another agent', async () => {
201
+ it('should return error when task is being reviewed by another agent', async () => {
190
202
  mockApiClient.claimValidation.mockResolvedValue({
191
203
  ok: false,
192
204
  error: 'Task is already being reviewed by another agent',
193
205
  });
194
206
  const ctx = createMockContext({ sessionId: 'session-123' });
195
207
 
196
- await expect(
197
- claimValidation({ task_id: VALID_UUID }, ctx)
198
- ).rejects.toThrow('Task is already being reviewed by another agent');
208
+ const result = await claimValidation({ task_id: VALID_UUID }, ctx);
209
+
210
+ expect(result.isError).toBe(true);
211
+ expect(result.result).toMatchObject({
212
+ error: 'Task is already being reviewed by another agent',
213
+ });
199
214
  });
200
215
 
201
216
  it('should allow same agent to re-claim their own review', async () => {
@@ -268,16 +283,19 @@ describe('claimValidation', () => {
268
283
  );
269
284
  });
270
285
 
271
- it('should throw error when claim fails', async () => {
286
+ it('should return error when claim fails', async () => {
272
287
  mockApiClient.claimValidation.mockResolvedValue({
273
288
  ok: false,
274
289
  error: 'Failed to claim task for validation',
275
290
  });
276
291
  const ctx = createMockContext();
277
292
 
278
- await expect(
279
- claimValidation({ task_id: VALID_UUID }, ctx)
280
- ).rejects.toThrow('Failed to claim task for validation');
293
+ const result = await claimValidation({ task_id: VALID_UUID }, ctx);
294
+
295
+ expect(result.isError).toBe(true);
296
+ expect(result.result).toMatchObject({
297
+ error: 'Failed to claim task for validation',
298
+ });
281
299
  });
282
300
  });
283
301
 
@@ -309,43 +327,52 @@ describe('validateTask', () => {
309
327
 
310
328
  await expect(
311
329
  validateTask({ task_id: VALID_UUID }, ctx)
312
- ).rejects.toThrow('approved is required');
330
+ ).rejects.toThrow(ValidationError);
313
331
  });
314
332
 
315
- it('should throw error when task not found', async () => {
333
+ it('should return error when task not found', async () => {
316
334
  mockApiClient.validateTask.mockResolvedValue({
317
335
  ok: false,
318
336
  error: 'Task not found',
319
337
  });
320
338
  const ctx = createMockContext();
321
339
 
322
- await expect(
323
- validateTask({ task_id: VALID_UUID, approved: true }, ctx)
324
- ).rejects.toThrow('Task not found');
340
+ const result = await validateTask({ task_id: VALID_UUID, approved: true }, ctx);
341
+
342
+ expect(result.isError).toBe(true);
343
+ expect(result.result).toMatchObject({
344
+ error: 'Task not found',
345
+ });
325
346
  });
326
347
 
327
- it('should throw error when task is not completed', async () => {
348
+ it('should return error when task is not completed', async () => {
328
349
  mockApiClient.validateTask.mockResolvedValue({
329
350
  ok: false,
330
351
  error: 'Can only validate completed tasks',
331
352
  });
332
353
  const ctx = createMockContext();
333
354
 
334
- await expect(
335
- validateTask({ task_id: VALID_UUID, approved: true }, ctx)
336
- ).rejects.toThrow('Can only validate completed tasks');
355
+ const result = await validateTask({ task_id: VALID_UUID, approved: true }, ctx);
356
+
357
+ expect(result.isError).toBe(true);
358
+ expect(result.result).toMatchObject({
359
+ error: 'Can only validate completed tasks',
360
+ });
337
361
  });
338
362
 
339
- it('should throw error when task already validated', async () => {
363
+ it('should return error when task already validated', async () => {
340
364
  mockApiClient.validateTask.mockResolvedValue({
341
365
  ok: false,
342
366
  error: 'Task has already been validated',
343
367
  });
344
368
  const ctx = createMockContext();
345
369
 
346
- await expect(
347
- validateTask({ task_id: VALID_UUID, approved: true }, ctx)
348
- ).rejects.toThrow('Task has already been validated');
370
+ const result = await validateTask({ task_id: VALID_UUID, approved: true }, ctx);
371
+
372
+ expect(result.isError).toBe(true);
373
+ expect(result.result).toMatchObject({
374
+ error: 'Task has already been validated',
375
+ });
349
376
  });
350
377
 
351
378
  describe('when approved', () => {
@@ -471,28 +498,34 @@ describe('validateTask', () => {
471
498
  });
472
499
  });
473
500
 
474
- it('should throw error when validation fails on approval', async () => {
501
+ it('should return error when validation fails on approval', async () => {
475
502
  mockApiClient.validateTask.mockResolvedValue({
476
503
  ok: false,
477
504
  error: 'Failed to validate task',
478
505
  });
479
506
  const ctx = createMockContext();
480
507
 
481
- await expect(
482
- validateTask({ task_id: VALID_UUID, approved: true }, ctx)
483
- ).rejects.toThrow('Failed to validate task');
508
+ const result = await validateTask({ task_id: VALID_UUID, approved: true }, ctx);
509
+
510
+ expect(result.isError).toBe(true);
511
+ expect(result.result).toMatchObject({
512
+ error: 'Failed to validate task',
513
+ });
484
514
  });
485
515
 
486
- it('should throw error when validation fails on rejection', async () => {
516
+ it('should return error when validation fails on rejection', async () => {
487
517
  mockApiClient.validateTask.mockResolvedValue({
488
518
  ok: false,
489
519
  error: 'Failed to validate task',
490
520
  });
491
521
  const ctx = createMockContext();
492
522
 
493
- await expect(
494
- validateTask({ task_id: VALID_UUID, approved: false }, ctx)
495
- ).rejects.toThrow('Failed to validate task');
523
+ const result = await validateTask({ task_id: VALID_UUID, approved: false }, ctx);
524
+
525
+ expect(result.isError).toBe(true);
526
+ expect(result.result).toMatchObject({
527
+ error: 'Failed to validate task',
528
+ });
496
529
  });
497
530
 
498
531
  describe('PR requirement', () => {
@@ -8,30 +8,40 @@
8
8
  */
9
9
 
10
10
  import type { Handler, HandlerRegistry } from './types.js';
11
- import { validateRequired, validateUUID } from '../validators.js';
11
+ import { parseArgs, uuidValidator } from '../validators.js';
12
12
  import { getApiClient } from '../api-client.js';
13
13
 
14
- export const getTasksAwaitingValidation: Handler = async (args, ctx) => {
15
- const { project_id } = args as { project_id: string };
14
+ // Argument schemas for type-safe parsing
15
+ const getTasksAwaitingValidationSchema = {
16
+ project_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
17
+ };
18
+
19
+ const claimValidationSchema = {
20
+ task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
21
+ };
16
22
 
17
- validateRequired(project_id, 'project_id');
18
- validateUUID(project_id, 'project_id');
23
+ const validateTaskSchema = {
24
+ task_id: { type: 'string' as const, required: true as const, validate: uuidValidator },
25
+ approved: { type: 'boolean' as const, required: true as const },
26
+ validation_notes: { type: 'string' as const },
27
+ skip_pr_check: { type: 'boolean' as const },
28
+ };
29
+
30
+ export const getTasksAwaitingValidation: Handler = async (args, _ctx) => {
31
+ const { project_id } = parseArgs(args, getTasksAwaitingValidationSchema);
19
32
 
20
33
  const apiClient = getApiClient();
21
34
  const response = await apiClient.getTasksAwaitingValidation(project_id);
22
35
 
23
36
  if (!response.ok) {
24
- throw new Error(response.error || 'Failed to fetch tasks awaiting validation');
37
+ return { result: { error: response.error || 'Failed to fetch tasks awaiting validation' }, isError: true };
25
38
  }
26
39
 
27
40
  return { result: response.data };
28
41
  };
29
42
 
30
43
  export const claimValidation: Handler = async (args, ctx) => {
31
- const { task_id } = args as { task_id: string };
32
-
33
- validateRequired(task_id, 'task_id');
34
- validateUUID(task_id, 'task_id');
44
+ const { task_id } = parseArgs(args, claimValidationSchema);
35
45
 
36
46
  const { session } = ctx;
37
47
  const currentSessionId = session.currentSessionId;
@@ -40,26 +50,14 @@ export const claimValidation: Handler = async (args, ctx) => {
40
50
  const response = await apiClient.claimValidation(task_id, currentSessionId || undefined);
41
51
 
42
52
  if (!response.ok) {
43
- throw new Error(response.error || 'Failed to claim task for validation');
53
+ return { result: { error: response.error || 'Failed to claim task for validation' }, isError: true };
44
54
  }
45
55
 
46
56
  return { result: response.data };
47
57
  };
48
58
 
49
59
  export const validateTask: Handler = async (args, ctx) => {
50
- const { task_id, validation_notes, approved, skip_pr_check } = args as {
51
- task_id: string;
52
- validation_notes?: string;
53
- approved: boolean;
54
- skip_pr_check?: boolean;
55
- };
56
-
57
- validateRequired(task_id, 'task_id');
58
- validateUUID(task_id, 'task_id');
59
-
60
- if (approved === undefined) {
61
- throw new Error('approved is required');
62
- }
60
+ const { task_id, approved, validation_notes, skip_pr_check } = parseArgs(args, validateTaskSchema);
63
61
 
64
62
  const { session } = ctx;
65
63
  const currentSessionId = session.currentSessionId;
@@ -83,7 +81,7 @@ export const validateTask: Handler = async (args, ctx) => {
83
81
  },
84
82
  };
85
83
  }
86
- throw new Error(response.error || 'Failed to validate task');
84
+ return { result: { error: response.error || 'Failed to validate task' }, isError: true };
87
85
  }
88
86
 
89
87
  return { result: response.data };