@vibescope/mcp-server 0.1.0 → 0.2.1

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 (76) hide show
  1. package/README.md +1 -1
  2. package/dist/api-client.d.ts +120 -2
  3. package/dist/api-client.js +51 -5
  4. package/dist/handlers/bodies-of-work.js +84 -50
  5. package/dist/handlers/cost.js +62 -54
  6. package/dist/handlers/decisions.js +29 -16
  7. package/dist/handlers/deployment.js +114 -107
  8. package/dist/handlers/discovery.d.ts +3 -0
  9. package/dist/handlers/discovery.js +55 -657
  10. package/dist/handlers/fallback.js +42 -28
  11. package/dist/handlers/file-checkouts.d.ts +18 -0
  12. package/dist/handlers/file-checkouts.js +101 -0
  13. package/dist/handlers/findings.d.ts +14 -1
  14. package/dist/handlers/findings.js +104 -28
  15. package/dist/handlers/git-issues.js +36 -32
  16. package/dist/handlers/ideas.js +44 -26
  17. package/dist/handlers/index.d.ts +2 -0
  18. package/dist/handlers/index.js +6 -0
  19. package/dist/handlers/milestones.js +34 -27
  20. package/dist/handlers/organizations.js +86 -78
  21. package/dist/handlers/progress.js +22 -11
  22. package/dist/handlers/project.js +62 -22
  23. package/dist/handlers/requests.js +15 -11
  24. package/dist/handlers/roles.d.ts +18 -0
  25. package/dist/handlers/roles.js +130 -0
  26. package/dist/handlers/session.js +52 -15
  27. package/dist/handlers/sprints.js +78 -65
  28. package/dist/handlers/tasks.js +135 -74
  29. package/dist/handlers/tool-docs.d.ts +4 -3
  30. package/dist/handlers/tool-docs.js +252 -5
  31. package/dist/handlers/validation.js +30 -14
  32. package/dist/index.js +25 -7
  33. package/dist/tools.js +417 -4
  34. package/package.json +1 -1
  35. package/src/api-client.ts +161 -8
  36. package/src/handlers/__test-setup__.ts +12 -0
  37. package/src/handlers/bodies-of-work.ts +127 -111
  38. package/src/handlers/cost.test.ts +34 -44
  39. package/src/handlers/cost.ts +77 -92
  40. package/src/handlers/decisions.test.ts +3 -2
  41. package/src/handlers/decisions.ts +32 -27
  42. package/src/handlers/deployment.ts +144 -190
  43. package/src/handlers/discovery.test.ts +4 -5
  44. package/src/handlers/discovery.ts +60 -746
  45. package/src/handlers/fallback.test.ts +78 -0
  46. package/src/handlers/fallback.ts +51 -38
  47. package/src/handlers/file-checkouts.test.ts +477 -0
  48. package/src/handlers/file-checkouts.ts +127 -0
  49. package/src/handlers/findings.test.ts +274 -2
  50. package/src/handlers/findings.ts +123 -57
  51. package/src/handlers/git-issues.ts +40 -80
  52. package/src/handlers/ideas.ts +56 -54
  53. package/src/handlers/index.ts +6 -0
  54. package/src/handlers/milestones.test.ts +1 -1
  55. package/src/handlers/milestones.ts +47 -45
  56. package/src/handlers/organizations.ts +104 -129
  57. package/src/handlers/progress.ts +24 -22
  58. package/src/handlers/project.ts +89 -57
  59. package/src/handlers/requests.ts +18 -14
  60. package/src/handlers/roles.test.ts +303 -0
  61. package/src/handlers/roles.ts +208 -0
  62. package/src/handlers/session.test.ts +37 -2
  63. package/src/handlers/session.ts +64 -21
  64. package/src/handlers/sprints.ts +114 -134
  65. package/src/handlers/tasks.test.ts +61 -0
  66. package/src/handlers/tasks.ts +170 -139
  67. package/src/handlers/tool-docs.ts +1024 -0
  68. package/src/handlers/validation.test.ts +53 -1
  69. package/src/handlers/validation.ts +32 -21
  70. package/src/index.ts +25 -7
  71. package/src/tools.ts +417 -4
  72. package/dist/config/tool-categories.d.ts +0 -31
  73. package/dist/config/tool-categories.js +0 -253
  74. package/dist/knowledge.d.ts +0 -6
  75. package/dist/knowledge.js +0 -218
  76. package/src/knowledge.ts +0 -230
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Role Management Handlers
3
+ *
4
+ * Handles agent role configuration and assignment:
5
+ * - get_role_settings: Get role configuration for a project
6
+ * - update_role_settings: Update role settings for a project
7
+ * - set_session_role: Set the role for the current session
8
+ * - get_agents_by_role: Get active agents grouped by their assigned roles
9
+ */
10
+ import { getApiClient } from '../api-client.js';
11
+ export const getRoleSettings = async (args, _ctx) => {
12
+ const { project_id } = args;
13
+ if (!project_id) {
14
+ return {
15
+ result: { error: 'project_id is required' },
16
+ };
17
+ }
18
+ const apiClient = getApiClient();
19
+ const response = await apiClient.proxy('get_role_settings', { project_id });
20
+ if (!response.ok) {
21
+ return {
22
+ result: { error: response.error || 'Failed to get role settings' },
23
+ };
24
+ }
25
+ return { result: response.data };
26
+ };
27
+ export const updateRoleSettings = async (args, _ctx) => {
28
+ const { project_id, role, enabled, display_name, description, priority_filter, fallback_activities, auto_assign_validation, auto_assign_deployment, } = args;
29
+ if (!project_id) {
30
+ return {
31
+ result: { error: 'project_id is required' },
32
+ };
33
+ }
34
+ if (!role) {
35
+ return {
36
+ result: { error: 'role is required' },
37
+ };
38
+ }
39
+ const apiClient = getApiClient();
40
+ const response = await apiClient.proxy('update_role_settings', {
41
+ project_id,
42
+ role,
43
+ enabled,
44
+ display_name,
45
+ description,
46
+ priority_filter,
47
+ fallback_activities,
48
+ auto_assign_validation,
49
+ auto_assign_deployment,
50
+ });
51
+ if (!response.ok) {
52
+ return {
53
+ result: { error: response.error || 'Failed to update role settings' },
54
+ };
55
+ }
56
+ return { result: response.data };
57
+ };
58
+ export const setSessionRole = async (args, ctx) => {
59
+ const { role, role_config } = args;
60
+ const { session, updateSession } = ctx;
61
+ if (!role) {
62
+ return {
63
+ result: { error: 'role is required' },
64
+ };
65
+ }
66
+ const validRoles = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'];
67
+ if (!validRoles.includes(role)) {
68
+ return {
69
+ result: {
70
+ error: `Invalid role: ${role}. Must be one of: ${validRoles.join(', ')}`,
71
+ },
72
+ };
73
+ }
74
+ // Update local session state
75
+ updateSession({ currentRole: role });
76
+ // If there's an active session, update it on the server too
77
+ if (session.currentSessionId) {
78
+ const apiClient = getApiClient();
79
+ const response = await apiClient.proxy('set_session_role', {
80
+ session_id: session.currentSessionId,
81
+ role,
82
+ role_config,
83
+ });
84
+ if (!response.ok) {
85
+ return {
86
+ result: { error: response.error || 'Failed to update session role' },
87
+ };
88
+ }
89
+ return {
90
+ result: {
91
+ success: true,
92
+ session_id: session.currentSessionId,
93
+ role,
94
+ message: `Session role updated to ${role}. Task filtering and fallback suggestions will now be optimized for this role.`,
95
+ },
96
+ };
97
+ }
98
+ return {
99
+ result: {
100
+ success: true,
101
+ role,
102
+ message: `Local role set to ${role}. Start a session to persist this role.`,
103
+ },
104
+ };
105
+ };
106
+ export const getAgentsByRole = async (args, _ctx) => {
107
+ const { project_id } = args;
108
+ if (!project_id) {
109
+ return {
110
+ result: { error: 'project_id is required' },
111
+ };
112
+ }
113
+ const apiClient = getApiClient();
114
+ const response = await apiClient.proxy('get_agents_by_role', { project_id });
115
+ if (!response.ok) {
116
+ return {
117
+ result: { error: response.error || 'Failed to get agents by role' },
118
+ };
119
+ }
120
+ return { result: response.data };
121
+ };
122
+ /**
123
+ * Role handlers registry
124
+ */
125
+ export const roleHandlers = {
126
+ get_role_settings: getRoleSettings,
127
+ update_role_settings: updateRoleSettings,
128
+ set_session_role: setSessionRole,
129
+ get_agents_by_role: getAgentsByRole,
130
+ };
@@ -8,14 +8,35 @@
8
8
  * - get_help
9
9
  * - get_token_usage
10
10
  */
11
- import { KNOWLEDGE_BASE } from '../knowledge.js';
11
+ import { parseArgs, createEnumValidator } from '../validators.js';
12
12
  import { getApiClient } from '../api-client.js';
13
+ const VALID_MODES = ['lite', 'full'];
14
+ const VALID_MODELS = ['opus', 'sonnet', 'haiku'];
15
+ const VALID_ROLES = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'];
16
+ // Argument schemas for type-safe parsing
17
+ const startWorkSessionSchema = {
18
+ project_id: { type: 'string' },
19
+ git_url: { type: 'string' },
20
+ mode: { type: 'string', default: 'lite', validate: createEnumValidator(VALID_MODES) },
21
+ model: { type: 'string', validate: createEnumValidator(VALID_MODELS) },
22
+ role: { type: 'string', default: 'developer', validate: createEnumValidator(VALID_ROLES) },
23
+ };
24
+ const heartbeatSchema = {
25
+ session_id: { type: 'string' },
26
+ current_worktree_path: { type: 'string' },
27
+ };
28
+ const endWorkSessionSchema = {
29
+ session_id: { type: 'string' },
30
+ };
31
+ const getHelpSchema = {
32
+ topic: { type: 'string', required: true },
33
+ };
13
34
  export const startWorkSession = async (args, ctx) => {
14
- const { project_id, git_url, mode = 'lite', model, role = 'developer' } = args;
35
+ const { project_id, git_url, mode, model, role } = parseArgs(args, startWorkSessionSchema);
15
36
  const { session, updateSession } = ctx;
16
37
  // Reset token tracking for new session with model info
17
38
  const normalizedModel = model ? model.toLowerCase().replace(/^claude[- ]*/i, '') : null;
18
- const validModel = normalizedModel && ['opus', 'sonnet', 'haiku'].includes(normalizedModel)
39
+ const validModel = normalizedModel && VALID_MODELS.includes(normalizedModel)
19
40
  ? normalizedModel
20
41
  : null;
21
42
  updateSession({
@@ -39,9 +60,9 @@ export const startWorkSession = async (args, ctx) => {
39
60
  const response = await apiClient.startSession({
40
61
  project_id,
41
62
  git_url,
42
- mode,
43
- model,
44
- role
63
+ mode: mode,
64
+ model: model,
65
+ role: role
45
66
  });
46
67
  if (!response.ok) {
47
68
  return {
@@ -114,7 +135,7 @@ export const startWorkSession = async (args, ctx) => {
114
135
  return { result };
115
136
  };
116
137
  export const heartbeat = async (args, ctx) => {
117
- const { session_id } = args;
138
+ const { session_id, current_worktree_path } = parseArgs(args, heartbeatSchema);
118
139
  const { session } = ctx;
119
140
  const targetSession = session_id || session.currentSessionId;
120
141
  if (!targetSession) {
@@ -125,8 +146,10 @@ export const heartbeat = async (args, ctx) => {
125
146
  };
126
147
  }
127
148
  const apiClient = getApiClient();
128
- // Send heartbeat
129
- const heartbeatResponse = await apiClient.heartbeat(targetSession);
149
+ // Send heartbeat with optional worktree path
150
+ const heartbeatResponse = await apiClient.heartbeat(targetSession, {
151
+ current_worktree_path,
152
+ });
130
153
  if (!heartbeatResponse.ok) {
131
154
  return {
132
155
  result: {
@@ -149,7 +172,7 @@ export const heartbeat = async (args, ctx) => {
149
172
  };
150
173
  };
151
174
  export const endWorkSession = async (args, ctx) => {
152
- const { session_id } = args;
175
+ const { session_id } = parseArgs(args, endWorkSessionSchema);
153
176
  const { session, updateSession } = ctx;
154
177
  const targetSession = session_id || session.currentSessionId;
155
178
  if (!targetSession) {
@@ -204,17 +227,31 @@ export const endWorkSession = async (args, ctx) => {
204
227
  };
205
228
  };
206
229
  export const getHelp = async (args, _ctx) => {
207
- const { topic } = args;
208
- const content = KNOWLEDGE_BASE[topic];
209
- if (!content) {
230
+ const { topic } = parseArgs(args, getHelpSchema);
231
+ const apiClient = getApiClient();
232
+ const response = await apiClient.getHelpTopic(topic);
233
+ if (!response.ok) {
234
+ // If database fetch fails, return error
235
+ return {
236
+ result: {
237
+ error: response.error || `Failed to fetch help topic: ${topic}`,
238
+ },
239
+ };
240
+ }
241
+ if (!response.data) {
242
+ // Topic not found - fetch available topics
243
+ const topicsResponse = await apiClient.getHelpTopics();
244
+ const available = topicsResponse.ok && topicsResponse.data
245
+ ? topicsResponse.data.map(t => t.slug)
246
+ : ['getting_started', 'tasks', 'validation', 'deployment', 'git', 'blockers', 'milestones', 'fallback', 'session', 'tokens', 'sprints', 'topics'];
210
247
  return {
211
248
  result: {
212
249
  error: `Unknown topic: ${topic}`,
213
- available: Object.keys(KNOWLEDGE_BASE),
250
+ available,
214
251
  },
215
252
  };
216
253
  }
217
- return { result: { topic, content } };
254
+ return { result: { topic, content: response.data.content } };
218
255
  };
219
256
  // Model pricing rates (USD per 1M tokens)
220
257
  const MODEL_PRICING = {
@@ -14,25 +14,76 @@
14
14
  * - get_sprint_backlog
15
15
  * - get_sprint_velocity
16
16
  */
17
- import { validateRequired, validateUUID, validateEnum } from '../validators.js';
17
+ import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
18
18
  import { getApiClient } from '../api-client.js';
19
19
  const SPRINT_STATUSES = ['planning', 'active', 'in_review', 'retrospective', 'completed', 'cancelled'];
20
20
  const TASK_PHASES = ['pre', 'core', 'post'];
21
21
  const DEPLOY_ENVIRONMENTS = ['development', 'staging', 'production'];
22
22
  const VERSION_BUMPS = ['patch', 'minor', 'major'];
23
+ // ============================================================================
24
+ // Argument Schemas
25
+ // ============================================================================
26
+ const createSprintSchema = {
27
+ project_id: { type: 'string', required: true, validate: uuidValidator },
28
+ title: { type: 'string', required: true },
29
+ goal: { type: 'string' },
30
+ start_date: { type: 'string', required: true },
31
+ end_date: { type: 'string', required: true },
32
+ auto_deploy_on_completion: { type: 'boolean' },
33
+ deploy_environment: { type: 'string', validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
34
+ deploy_version_bump: { type: 'string', validate: createEnumValidator(VERSION_BUMPS) },
35
+ };
36
+ const updateSprintSchema = {
37
+ sprint_id: { type: 'string', required: true, validate: uuidValidator },
38
+ title: { type: 'string' },
39
+ goal: { type: 'string' },
40
+ start_date: { type: 'string' },
41
+ end_date: { type: 'string' },
42
+ auto_deploy_on_completion: { type: 'boolean' },
43
+ deploy_environment: { type: 'string', validate: createEnumValidator(DEPLOY_ENVIRONMENTS) },
44
+ deploy_version_bump: { type: 'string', validate: createEnumValidator(VERSION_BUMPS) },
45
+ };
46
+ const getSprintSchema = {
47
+ sprint_id: { type: 'string', required: true, validate: uuidValidator },
48
+ summary_only: { type: 'boolean', default: false },
49
+ };
50
+ const getSprintsSchema = {
51
+ project_id: { type: 'string', required: true, validate: uuidValidator },
52
+ status: { type: 'string', validate: createEnumValidator(SPRINT_STATUSES) },
53
+ limit: { type: 'number', default: 20 },
54
+ offset: { type: 'number', default: 0 },
55
+ };
56
+ const deleteSprintSchema = {
57
+ sprint_id: { type: 'string', required: true, validate: uuidValidator },
58
+ };
59
+ const startSprintSchema = {
60
+ sprint_id: { type: 'string', required: true, validate: uuidValidator },
61
+ };
62
+ const completeSprintSchema = {
63
+ sprint_id: { type: 'string', required: true, validate: uuidValidator },
64
+ retrospective_notes: { type: 'string' },
65
+ skip_retrospective: { type: 'boolean' },
66
+ };
67
+ const addTaskToSprintSchema = {
68
+ sprint_id: { type: 'string', required: true, validate: uuidValidator },
69
+ task_id: { type: 'string', required: true, validate: uuidValidator },
70
+ story_points: { type: 'number' },
71
+ phase: { type: 'string', validate: createEnumValidator(TASK_PHASES) },
72
+ };
73
+ const removeTaskFromSprintSchema = {
74
+ sprint_id: { type: 'string', required: true, validate: uuidValidator },
75
+ task_id: { type: 'string', required: true, validate: uuidValidator },
76
+ };
77
+ const getSprintBacklogSchema = {
78
+ project_id: { type: 'string', required: true, validate: uuidValidator },
79
+ sprint_id: { type: 'string', validate: uuidValidator },
80
+ };
81
+ const getSprintVelocitySchema = {
82
+ project_id: { type: 'string', required: true, validate: uuidValidator },
83
+ limit: { type: 'number', default: 10 },
84
+ };
23
85
  export const createSprint = async (args, ctx) => {
24
- const { project_id, title, goal, start_date, end_date, auto_deploy_on_completion, deploy_environment, deploy_version_bump, } = args;
25
- validateRequired(project_id, 'project_id');
26
- validateUUID(project_id, 'project_id');
27
- validateRequired(title, 'title');
28
- validateRequired(start_date, 'start_date');
29
- validateRequired(end_date, 'end_date');
30
- if (deploy_environment) {
31
- validateEnum(deploy_environment, DEPLOY_ENVIRONMENTS, 'deploy_environment');
32
- }
33
- if (deploy_version_bump) {
34
- validateEnum(deploy_version_bump, VERSION_BUMPS, 'deploy_version_bump');
35
- }
86
+ const { project_id, title, goal, start_date, end_date, auto_deploy_on_completion, deploy_environment, deploy_version_bump, } = parseArgs(args, createSprintSchema);
36
87
  // Validate date format and order
37
88
  const startDateObj = new Date(start_date);
38
89
  const endDateObj = new Date(end_date);
@@ -78,15 +129,7 @@ export const createSprint = async (args, ctx) => {
78
129
  };
79
130
  };
80
131
  export const updateSprint = async (args, ctx) => {
81
- const { sprint_id, title, goal, start_date, end_date, auto_deploy_on_completion, deploy_environment, deploy_version_bump, } = args;
82
- validateRequired(sprint_id, 'sprint_id');
83
- validateUUID(sprint_id, 'sprint_id');
84
- if (deploy_environment) {
85
- validateEnum(deploy_environment, DEPLOY_ENVIRONMENTS, 'deploy_environment');
86
- }
87
- if (deploy_version_bump) {
88
- validateEnum(deploy_version_bump, VERSION_BUMPS, 'deploy_version_bump');
89
- }
132
+ const { sprint_id, title, goal, start_date, end_date, auto_deploy_on_completion, deploy_environment, deploy_version_bump, } = parseArgs(args, updateSprintSchema);
90
133
  // Validate dates if provided
91
134
  if (start_date) {
92
135
  const startDateObj = new Date(start_date);
@@ -117,28 +160,22 @@ export const updateSprint = async (args, ctx) => {
117
160
  return { result: { success: true, sprint_id } };
118
161
  };
119
162
  export const getSprint = async (args, ctx) => {
120
- const { sprint_id } = args;
121
- validateRequired(sprint_id, 'sprint_id');
122
- validateUUID(sprint_id, 'sprint_id');
163
+ const { sprint_id, summary_only } = parseArgs(args, getSprintSchema);
123
164
  const apiClient = getApiClient();
124
- const response = await apiClient.proxy('get_sprint', { sprint_id });
165
+ // Response type varies based on summary_only
166
+ const response = await apiClient.proxy('get_sprint', { sprint_id, summary_only });
125
167
  if (!response.ok) {
126
168
  throw new Error(`Failed to get sprint: ${response.error}`);
127
169
  }
128
170
  return { result: response.data };
129
171
  };
130
172
  export const getSprints = async (args, ctx) => {
131
- const { project_id, status, limit = 20, offset = 0 } = args;
132
- validateRequired(project_id, 'project_id');
133
- validateUUID(project_id, 'project_id');
134
- if (status) {
135
- validateEnum(status, SPRINT_STATUSES, 'status');
136
- }
173
+ const { project_id, status, limit, offset } = parseArgs(args, getSprintsSchema);
137
174
  const apiClient = getApiClient();
138
175
  const response = await apiClient.proxy('get_sprints', {
139
176
  project_id,
140
177
  status,
141
- limit: Math.min(limit, 100),
178
+ limit: Math.min(limit ?? 20, 100),
142
179
  offset,
143
180
  });
144
181
  if (!response.ok) {
@@ -147,9 +184,7 @@ export const getSprints = async (args, ctx) => {
147
184
  return { result: response.data };
148
185
  };
149
186
  export const deleteSprint = async (args, ctx) => {
150
- const { sprint_id } = args;
151
- validateRequired(sprint_id, 'sprint_id');
152
- validateUUID(sprint_id, 'sprint_id');
187
+ const { sprint_id } = parseArgs(args, deleteSprintSchema);
153
188
  const apiClient = getApiClient();
154
189
  const response = await apiClient.proxy('delete_sprint', {
155
190
  sprint_id
@@ -160,9 +195,7 @@ export const deleteSprint = async (args, ctx) => {
160
195
  return { result: { success: true, message: 'Sprint deleted. Tasks are preserved.' } };
161
196
  };
162
197
  export const startSprint = async (args, ctx) => {
163
- const { sprint_id } = args;
164
- validateRequired(sprint_id, 'sprint_id');
165
- validateUUID(sprint_id, 'sprint_id');
198
+ const { sprint_id } = parseArgs(args, startSprintSchema);
166
199
  const apiClient = getApiClient();
167
200
  const response = await apiClient.proxy('start_sprint', { sprint_id });
168
201
  if (!response.ok) {
@@ -171,9 +204,7 @@ export const startSprint = async (args, ctx) => {
171
204
  return { result: response.data };
172
205
  };
173
206
  export const completeSprint = async (args, ctx) => {
174
- const { sprint_id, retrospective_notes, skip_retrospective } = args;
175
- validateRequired(sprint_id, 'sprint_id');
176
- validateUUID(sprint_id, 'sprint_id');
207
+ const { sprint_id, retrospective_notes, skip_retrospective } = parseArgs(args, completeSprintSchema);
177
208
  const apiClient = getApiClient();
178
209
  const response = await apiClient.proxy('complete_sprint', {
179
210
  sprint_id,
@@ -186,14 +217,7 @@ export const completeSprint = async (args, ctx) => {
186
217
  return { result: response.data };
187
218
  };
188
219
  export const addTaskToSprint = async (args, ctx) => {
189
- const { sprint_id, task_id, story_points, phase } = args;
190
- validateRequired(sprint_id, 'sprint_id');
191
- validateUUID(sprint_id, 'sprint_id');
192
- validateRequired(task_id, 'task_id');
193
- validateUUID(task_id, 'task_id');
194
- if (phase) {
195
- validateEnum(phase, TASK_PHASES, 'phase');
196
- }
220
+ const { sprint_id, task_id, story_points, phase } = parseArgs(args, addTaskToSprintSchema);
197
221
  if (story_points !== undefined && (story_points < 0 || !Number.isInteger(story_points))) {
198
222
  throw new Error('story_points must be a non-negative integer');
199
223
  }
@@ -210,11 +234,7 @@ export const addTaskToSprint = async (args, ctx) => {
210
234
  return { result: response.data };
211
235
  };
212
236
  export const removeTaskFromSprint = async (args, ctx) => {
213
- const { sprint_id, task_id } = args;
214
- validateRequired(sprint_id, 'sprint_id');
215
- validateUUID(sprint_id, 'sprint_id');
216
- validateRequired(task_id, 'task_id');
217
- validateUUID(task_id, 'task_id');
237
+ const { sprint_id, task_id } = parseArgs(args, removeTaskFromSprintSchema);
218
238
  const apiClient = getApiClient();
219
239
  const response = await apiClient.proxy('remove_task_from_sprint', {
220
240
  sprint_id,
@@ -226,12 +246,7 @@ export const removeTaskFromSprint = async (args, ctx) => {
226
246
  return { result: response.data };
227
247
  };
228
248
  export const getSprintBacklog = async (args, ctx) => {
229
- const { project_id, sprint_id } = args;
230
- validateRequired(project_id, 'project_id');
231
- validateUUID(project_id, 'project_id');
232
- if (sprint_id) {
233
- validateUUID(sprint_id, 'sprint_id');
234
- }
249
+ const { project_id, sprint_id } = parseArgs(args, getSprintBacklogSchema);
235
250
  const apiClient = getApiClient();
236
251
  const response = await apiClient.proxy('get_sprint_backlog', {
237
252
  project_id,
@@ -243,13 +258,11 @@ export const getSprintBacklog = async (args, ctx) => {
243
258
  return { result: response.data };
244
259
  };
245
260
  export const getSprintVelocity = async (args, ctx) => {
246
- const { project_id, limit = 10 } = args;
247
- validateRequired(project_id, 'project_id');
248
- validateUUID(project_id, 'project_id');
261
+ const { project_id, limit } = parseArgs(args, getSprintVelocitySchema);
249
262
  const apiClient = getApiClient();
250
263
  const response = await apiClient.proxy('get_sprint_velocity', {
251
264
  project_id,
252
- limit: Math.min(limit, 50),
265
+ limit: Math.min(limit ?? 10, 50),
253
266
  });
254
267
  if (!response.ok) {
255
268
  throw new Error(`Failed to get sprint velocity: ${response.error}`);