@specforge/mcp 1.1.0
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/README.md +345 -0
- package/bin/specforge-mcp +3 -0
- package/dist/client/api-client.d.ts +110 -0
- package/dist/client/api-client.d.ts.map +1 -0
- package/dist/client/api-client.js +170 -0
- package/dist/client/api-client.js.map +1 -0
- package/dist/config/index.d.ts +71 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +120 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +100 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/index.d.ts +79 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +1622 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +329 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/validation/index.d.ts +86 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +442 -0
- package/dist/validation/index.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,1622 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools Registry
|
|
3
|
+
*
|
|
4
|
+
* This module defines and exports all available MCP tools.
|
|
5
|
+
* All operations from Epics 3-10 are registered here with proper JSON Schema definitions.
|
|
6
|
+
*
|
|
7
|
+
* Tool categories:
|
|
8
|
+
* - Core Operations: Project, Specification, Epic, Ticket, Dependency
|
|
9
|
+
* - Context & AI: Implementation context, actionable tickets, critical path
|
|
10
|
+
* - Workflow: Work sessions, progress reporting
|
|
11
|
+
* - Testing: Test results, validation
|
|
12
|
+
* - Discovery: Report and resolve discoveries
|
|
13
|
+
* - Status: Status reports, summaries, analytics
|
|
14
|
+
* - Search: Search, find by file/tag, find related
|
|
15
|
+
* - Git: Link commits and PRs, get linked items
|
|
16
|
+
*/
|
|
17
|
+
import { ValidationError, ApiError, validateToolArgs, formatMCPError, transformError, } from '../validation/index.js';
|
|
18
|
+
/**
|
|
19
|
+
* Get list of all available tools
|
|
20
|
+
*
|
|
21
|
+
* This returns the tool definitions that will be sent to the AI client
|
|
22
|
+
* in response to ListTools requests.
|
|
23
|
+
*
|
|
24
|
+
* @returns Array of tool definitions
|
|
25
|
+
*/
|
|
26
|
+
export function getTools() {
|
|
27
|
+
return [
|
|
28
|
+
// ========================================================================
|
|
29
|
+
// Core Operations - Projects (Epic 3)
|
|
30
|
+
// ========================================================================
|
|
31
|
+
{
|
|
32
|
+
name: 'list_projects',
|
|
33
|
+
description: 'List all projects accessible to the current user',
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {},
|
|
37
|
+
required: [],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'get_project',
|
|
42
|
+
description: 'Get details of a specific project including summary counts',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
projectId: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'The ID of the project to retrieve',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ['projectId'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
// ========================================================================
|
|
55
|
+
// Core Operations - Specifications (Epic 3)
|
|
56
|
+
// ========================================================================
|
|
57
|
+
{
|
|
58
|
+
name: 'list_specifications',
|
|
59
|
+
description: 'List specifications for a project with optional status filter',
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
projectId: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'The ID of the project',
|
|
66
|
+
},
|
|
67
|
+
status: {
|
|
68
|
+
type: 'array',
|
|
69
|
+
items: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
enum: ['draft', 'planning', 'ready', 'in_progress', 'completed'],
|
|
72
|
+
},
|
|
73
|
+
description: 'Filter by status (optional)',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
required: ['projectId'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'get_specification',
|
|
81
|
+
description: 'Get full specification with goals, requirements, constraints, and guardrails',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
specificationId: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'The ID of the specification to retrieve',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ['specificationId'],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
// ========================================================================
|
|
94
|
+
// Core Operations - Epics (Epic 3)
|
|
95
|
+
// ========================================================================
|
|
96
|
+
{
|
|
97
|
+
name: 'list_epics',
|
|
98
|
+
description: 'List epics for a specification with optional status filter',
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {
|
|
102
|
+
specificationId: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'The ID of the specification',
|
|
105
|
+
},
|
|
106
|
+
status: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
enum: ['todo', 'in_progress', 'completed'],
|
|
109
|
+
description: 'Filter by status (optional)',
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
required: ['specificationId'],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'get_epic',
|
|
117
|
+
description: 'Get epic with scope, goals, acceptance criteria, and ticket summary',
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
epicId: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
description: 'The ID of the epic to retrieve',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ['epicId'],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
// ========================================================================
|
|
130
|
+
// Core Operations - Tickets (Epic 3)
|
|
131
|
+
// ========================================================================
|
|
132
|
+
{
|
|
133
|
+
name: 'list_tickets',
|
|
134
|
+
description: 'List tickets for an epic with optional status filter',
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
properties: {
|
|
138
|
+
epicId: {
|
|
139
|
+
type: 'string',
|
|
140
|
+
description: 'The ID of the epic',
|
|
141
|
+
},
|
|
142
|
+
status: {
|
|
143
|
+
type: 'array',
|
|
144
|
+
items: {
|
|
145
|
+
type: 'string',
|
|
146
|
+
enum: ['todo', 'queue', 'in_progress', 'blocked', 'done'],
|
|
147
|
+
},
|
|
148
|
+
description: 'Filter by status (optional)',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
required: ['epicId'],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'get_ticket',
|
|
156
|
+
description: 'Get full ticket details including implementation steps, acceptance criteria, and dependencies',
|
|
157
|
+
inputSchema: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
properties: {
|
|
160
|
+
ticketId: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
description: 'The ID of the ticket to retrieve',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
required: ['ticketId'],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
// ========================================================================
|
|
169
|
+
// Core Operations - Dependencies (Epic 3)
|
|
170
|
+
// ========================================================================
|
|
171
|
+
{
|
|
172
|
+
name: 'get_ticket_dependencies',
|
|
173
|
+
description: 'Get dependency information for a ticket - what it blocks and what blocks it',
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
ticketId: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'The ID of the ticket',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
required: ['ticketId'],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
// ========================================================================
|
|
186
|
+
// Context & AI Tools (Epic 4)
|
|
187
|
+
// ========================================================================
|
|
188
|
+
{
|
|
189
|
+
name: 'get_implementation_context',
|
|
190
|
+
description: 'Get full implementation context for a ticket including spec, epic, dependencies, and patterns',
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: 'object',
|
|
193
|
+
properties: {
|
|
194
|
+
ticketId: {
|
|
195
|
+
type: 'string',
|
|
196
|
+
description: 'The ID of the ticket',
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
required: ['ticketId'],
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'get_next_actionable_tickets',
|
|
204
|
+
description: 'Get tickets that can be worked on now (all dependencies satisfied)',
|
|
205
|
+
inputSchema: {
|
|
206
|
+
type: 'object',
|
|
207
|
+
properties: {
|
|
208
|
+
specificationId: {
|
|
209
|
+
type: 'string',
|
|
210
|
+
description: 'The ID of the specification',
|
|
211
|
+
},
|
|
212
|
+
limit: {
|
|
213
|
+
type: 'number',
|
|
214
|
+
description: 'Maximum number of tickets to return (default: 5)',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
required: ['specificationId'],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'get_blocked_tickets',
|
|
222
|
+
description: 'Get tickets that are blocked with reasons why',
|
|
223
|
+
inputSchema: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {
|
|
226
|
+
specificationId: {
|
|
227
|
+
type: 'string',
|
|
228
|
+
description: 'The ID of the specification',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
required: ['specificationId'],
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'get_critical_path',
|
|
236
|
+
description: 'Get the critical path (longest dependency chain) for a specification',
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {
|
|
240
|
+
specificationId: {
|
|
241
|
+
type: 'string',
|
|
242
|
+
description: 'The ID of the specification',
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
required: ['specificationId'],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: 'get_patterns',
|
|
250
|
+
description: 'Extract tech stack, code patterns, and naming conventions from specification',
|
|
251
|
+
inputSchema: {
|
|
252
|
+
type: 'object',
|
|
253
|
+
properties: {
|
|
254
|
+
specificationId: {
|
|
255
|
+
type: 'string',
|
|
256
|
+
description: 'The ID of the specification',
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
required: ['specificationId'],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
// ========================================================================
|
|
263
|
+
// Workflow & Tracking (Epic 5)
|
|
264
|
+
// ========================================================================
|
|
265
|
+
{
|
|
266
|
+
name: 'start_work_session',
|
|
267
|
+
description: 'Start working on a ticket - validates dependencies are satisfied',
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: 'object',
|
|
270
|
+
properties: {
|
|
271
|
+
ticketId: {
|
|
272
|
+
type: 'string',
|
|
273
|
+
description: 'The ID of the ticket to start',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
required: ['ticketId'],
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: 'complete_work_session',
|
|
281
|
+
description: 'Mark a ticket as complete with summary and results',
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
properties: {
|
|
285
|
+
ticketId: {
|
|
286
|
+
type: 'string',
|
|
287
|
+
description: 'The ID of the ticket',
|
|
288
|
+
},
|
|
289
|
+
summary: {
|
|
290
|
+
type: 'string',
|
|
291
|
+
description: 'Summary of work completed',
|
|
292
|
+
},
|
|
293
|
+
filesModified: {
|
|
294
|
+
type: 'array',
|
|
295
|
+
items: { type: 'string' },
|
|
296
|
+
description: 'List of files modified',
|
|
297
|
+
},
|
|
298
|
+
filesCreated: {
|
|
299
|
+
type: 'array',
|
|
300
|
+
items: { type: 'string' },
|
|
301
|
+
description: 'List of files created',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
required: ['ticketId', 'summary'],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: 'report_progress',
|
|
309
|
+
description: 'Report incremental progress on a ticket',
|
|
310
|
+
inputSchema: {
|
|
311
|
+
type: 'object',
|
|
312
|
+
properties: {
|
|
313
|
+
ticketId: {
|
|
314
|
+
type: 'string',
|
|
315
|
+
description: 'The ID of the ticket',
|
|
316
|
+
},
|
|
317
|
+
progress: {
|
|
318
|
+
type: 'number',
|
|
319
|
+
minimum: 0,
|
|
320
|
+
maximum: 100,
|
|
321
|
+
description: 'Progress percentage (0-100)',
|
|
322
|
+
},
|
|
323
|
+
message: {
|
|
324
|
+
type: 'string',
|
|
325
|
+
description: 'Progress message',
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
required: ['ticketId', 'progress'],
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: 'get_work_summary',
|
|
333
|
+
description: 'Get summary of completed work with optional scope filtering',
|
|
334
|
+
inputSchema: {
|
|
335
|
+
type: 'object',
|
|
336
|
+
properties: {
|
|
337
|
+
scope: {
|
|
338
|
+
type: 'string',
|
|
339
|
+
enum: ['epic', 'specification', 'project', 'user'],
|
|
340
|
+
description: 'Scope of the summary',
|
|
341
|
+
},
|
|
342
|
+
scopeId: {
|
|
343
|
+
type: 'string',
|
|
344
|
+
description: 'ID of the scoped entity (epicId, specificationId, or projectId)',
|
|
345
|
+
},
|
|
346
|
+
startDate: {
|
|
347
|
+
type: 'string',
|
|
348
|
+
description: 'Start date filter (ISO 8601)',
|
|
349
|
+
},
|
|
350
|
+
endDate: {
|
|
351
|
+
type: 'string',
|
|
352
|
+
description: 'End date filter (ISO 8601)',
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
required: ['scope'],
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
// ========================================================================
|
|
359
|
+
// Testing Tools (Epic 6)
|
|
360
|
+
// ========================================================================
|
|
361
|
+
{
|
|
362
|
+
name: 'report_test_results',
|
|
363
|
+
description: 'Report test results for a ticket',
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: 'object',
|
|
366
|
+
properties: {
|
|
367
|
+
ticketId: {
|
|
368
|
+
type: 'string',
|
|
369
|
+
description: 'The ID of the ticket',
|
|
370
|
+
},
|
|
371
|
+
passed: {
|
|
372
|
+
type: 'boolean',
|
|
373
|
+
description: 'Whether the tests passed',
|
|
374
|
+
},
|
|
375
|
+
testType: {
|
|
376
|
+
type: 'string',
|
|
377
|
+
enum: ['unit', 'integration', 'e2e', 'manual'],
|
|
378
|
+
description: 'Type of test',
|
|
379
|
+
},
|
|
380
|
+
summary: {
|
|
381
|
+
type: 'string',
|
|
382
|
+
description: 'Summary of test results',
|
|
383
|
+
},
|
|
384
|
+
output: {
|
|
385
|
+
type: 'string',
|
|
386
|
+
description: 'Test output (will be truncated if too long)',
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
required: ['ticketId', 'passed', 'testType'],
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: 'get_ticket_test_status',
|
|
394
|
+
description: 'Get test status and history for a ticket',
|
|
395
|
+
inputSchema: {
|
|
396
|
+
type: 'object',
|
|
397
|
+
properties: {
|
|
398
|
+
ticketId: {
|
|
399
|
+
type: 'string',
|
|
400
|
+
description: 'The ID of the ticket',
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
required: ['ticketId'],
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: 'validate_ticket_completion',
|
|
408
|
+
description: 'Validate that a ticket is ready to be marked complete',
|
|
409
|
+
inputSchema: {
|
|
410
|
+
type: 'object',
|
|
411
|
+
properties: {
|
|
412
|
+
ticketId: {
|
|
413
|
+
type: 'string',
|
|
414
|
+
description: 'The ID of the ticket',
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
required: ['ticketId'],
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
// ========================================================================
|
|
421
|
+
// Discovery Tools (Epic 7)
|
|
422
|
+
// ========================================================================
|
|
423
|
+
{
|
|
424
|
+
name: 'report_discovery',
|
|
425
|
+
description: 'Report a discovery (new requirement, bug, or improvement) found during implementation',
|
|
426
|
+
inputSchema: {
|
|
427
|
+
type: 'object',
|
|
428
|
+
properties: {
|
|
429
|
+
ticketId: {
|
|
430
|
+
type: 'string',
|
|
431
|
+
description: 'The ID of the ticket where discovery was made',
|
|
432
|
+
},
|
|
433
|
+
type: {
|
|
434
|
+
type: 'string',
|
|
435
|
+
enum: ['bug', 'requirement', 'improvement', 'question'],
|
|
436
|
+
description: 'Type of discovery',
|
|
437
|
+
},
|
|
438
|
+
title: {
|
|
439
|
+
type: 'string',
|
|
440
|
+
description: 'Short title for the discovery',
|
|
441
|
+
},
|
|
442
|
+
description: {
|
|
443
|
+
type: 'string',
|
|
444
|
+
description: 'Detailed description',
|
|
445
|
+
},
|
|
446
|
+
priority: {
|
|
447
|
+
type: 'string',
|
|
448
|
+
enum: ['low', 'medium', 'high', 'critical'],
|
|
449
|
+
description: 'Priority level',
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
required: ['ticketId', 'type', 'title'],
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: 'get_pending_discoveries',
|
|
457
|
+
description: 'Get pending discoveries that need resolution',
|
|
458
|
+
inputSchema: {
|
|
459
|
+
type: 'object',
|
|
460
|
+
properties: {
|
|
461
|
+
specificationId: {
|
|
462
|
+
type: 'string',
|
|
463
|
+
description: 'Filter by specification (optional)',
|
|
464
|
+
},
|
|
465
|
+
projectId: {
|
|
466
|
+
type: 'string',
|
|
467
|
+
description: 'Filter by project (optional)',
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
required: [],
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: 'resolve_discovery',
|
|
475
|
+
description: 'Resolve a discovery as created (new ticket) or dismissed',
|
|
476
|
+
inputSchema: {
|
|
477
|
+
type: 'object',
|
|
478
|
+
properties: {
|
|
479
|
+
discoveryId: {
|
|
480
|
+
type: 'string',
|
|
481
|
+
description: 'The ID of the discovery',
|
|
482
|
+
},
|
|
483
|
+
resolution: {
|
|
484
|
+
type: 'string',
|
|
485
|
+
enum: ['created', 'dismissed'],
|
|
486
|
+
description: 'Resolution type',
|
|
487
|
+
},
|
|
488
|
+
createdTicketId: {
|
|
489
|
+
type: 'string',
|
|
490
|
+
description: 'ID of created ticket (if resolution is "created")',
|
|
491
|
+
},
|
|
492
|
+
notes: {
|
|
493
|
+
type: 'string',
|
|
494
|
+
description: 'Resolution notes',
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
required: ['discoveryId', 'resolution'],
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
// ========================================================================
|
|
501
|
+
// Status & Analytics Tools (Epic 8)
|
|
502
|
+
// ========================================================================
|
|
503
|
+
{
|
|
504
|
+
name: 'get_specification_status',
|
|
505
|
+
description: 'Get detailed status for a specification including progress, time tracking, and blockers',
|
|
506
|
+
inputSchema: {
|
|
507
|
+
type: 'object',
|
|
508
|
+
properties: {
|
|
509
|
+
specificationId: {
|
|
510
|
+
type: 'string',
|
|
511
|
+
description: 'The ID of the specification',
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
required: ['specificationId'],
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: 'get_epic_status',
|
|
519
|
+
description: 'Get detailed status for an epic including progress and actionable tickets',
|
|
520
|
+
inputSchema: {
|
|
521
|
+
type: 'object',
|
|
522
|
+
properties: {
|
|
523
|
+
epicId: {
|
|
524
|
+
type: 'string',
|
|
525
|
+
description: 'The ID of the epic',
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
required: ['epicId'],
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
name: 'get_implementation_summary',
|
|
533
|
+
description: 'Get cross-specification implementation summary with velocity metrics',
|
|
534
|
+
inputSchema: {
|
|
535
|
+
type: 'object',
|
|
536
|
+
properties: {
|
|
537
|
+
projectId: {
|
|
538
|
+
type: 'string',
|
|
539
|
+
description: 'Filter by project (optional)',
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
required: [],
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: 'get_time_report',
|
|
547
|
+
description: 'Get time tracking report with estimated vs actual analysis',
|
|
548
|
+
inputSchema: {
|
|
549
|
+
type: 'object',
|
|
550
|
+
properties: {
|
|
551
|
+
scope: {
|
|
552
|
+
type: 'string',
|
|
553
|
+
enum: ['epic', 'specification', 'project'],
|
|
554
|
+
description: 'Scope of the report',
|
|
555
|
+
},
|
|
556
|
+
scopeId: {
|
|
557
|
+
type: 'string',
|
|
558
|
+
description: 'ID of the scoped entity',
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
required: ['scope', 'scopeId'],
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: 'get_blockers_report',
|
|
566
|
+
description: 'Get report of all blockers with duration and grouping',
|
|
567
|
+
inputSchema: {
|
|
568
|
+
type: 'object',
|
|
569
|
+
properties: {
|
|
570
|
+
specificationId: {
|
|
571
|
+
type: 'string',
|
|
572
|
+
description: 'Filter by specification (optional)',
|
|
573
|
+
},
|
|
574
|
+
projectId: {
|
|
575
|
+
type: 'string',
|
|
576
|
+
description: 'Filter by project (optional)',
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
required: [],
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
// ========================================================================
|
|
583
|
+
// Search Tools (Epic 9)
|
|
584
|
+
// ========================================================================
|
|
585
|
+
{
|
|
586
|
+
name: 'search_tickets',
|
|
587
|
+
description: 'Full-text search across tickets with relevance ranking',
|
|
588
|
+
inputSchema: {
|
|
589
|
+
type: 'object',
|
|
590
|
+
properties: {
|
|
591
|
+
query: {
|
|
592
|
+
type: 'string',
|
|
593
|
+
description: 'Search query',
|
|
594
|
+
},
|
|
595
|
+
specificationId: {
|
|
596
|
+
type: 'string',
|
|
597
|
+
description: 'Limit search to specification (optional)',
|
|
598
|
+
},
|
|
599
|
+
projectId: {
|
|
600
|
+
type: 'string',
|
|
601
|
+
description: 'Limit search to project (optional)',
|
|
602
|
+
},
|
|
603
|
+
limit: {
|
|
604
|
+
type: 'number',
|
|
605
|
+
description: 'Maximum results (default: 20)',
|
|
606
|
+
},
|
|
607
|
+
offset: {
|
|
608
|
+
type: 'number',
|
|
609
|
+
description: 'Pagination offset (default: 0)',
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
required: ['query'],
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
name: 'find_tickets_by_file',
|
|
617
|
+
description: 'Find tickets that modify or create specific files (supports glob patterns)',
|
|
618
|
+
inputSchema: {
|
|
619
|
+
type: 'object',
|
|
620
|
+
properties: {
|
|
621
|
+
filePath: {
|
|
622
|
+
type: 'string',
|
|
623
|
+
description: 'File path or glob pattern (e.g., "src/components/*.tsx")',
|
|
624
|
+
},
|
|
625
|
+
specificationId: {
|
|
626
|
+
type: 'string',
|
|
627
|
+
description: 'Limit search to specification (optional)',
|
|
628
|
+
},
|
|
629
|
+
projectId: {
|
|
630
|
+
type: 'string',
|
|
631
|
+
description: 'Limit search to project (optional)',
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
required: ['filePath'],
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
name: 'find_tickets_by_tag',
|
|
639
|
+
description: 'Find tickets by tags with AND/OR logic',
|
|
640
|
+
inputSchema: {
|
|
641
|
+
type: 'object',
|
|
642
|
+
properties: {
|
|
643
|
+
tags: {
|
|
644
|
+
type: 'array',
|
|
645
|
+
items: { type: 'string' },
|
|
646
|
+
description: 'Tags to search for',
|
|
647
|
+
},
|
|
648
|
+
matchAll: {
|
|
649
|
+
type: 'boolean',
|
|
650
|
+
description: 'If true, match all tags (AND). If false, match any (OR). Default: false',
|
|
651
|
+
},
|
|
652
|
+
specificationId: {
|
|
653
|
+
type: 'string',
|
|
654
|
+
description: 'Limit search to specification (optional)',
|
|
655
|
+
},
|
|
656
|
+
projectId: {
|
|
657
|
+
type: 'string',
|
|
658
|
+
description: 'Limit search to project (optional)',
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
required: ['tags'],
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
name: 'find_related_tickets',
|
|
666
|
+
description: 'Find tickets related to a given ticket based on files, tags, and tech stack',
|
|
667
|
+
inputSchema: {
|
|
668
|
+
type: 'object',
|
|
669
|
+
properties: {
|
|
670
|
+
ticketId: {
|
|
671
|
+
type: 'string',
|
|
672
|
+
description: 'The ID of the ticket',
|
|
673
|
+
},
|
|
674
|
+
limit: {
|
|
675
|
+
type: 'number',
|
|
676
|
+
description: 'Maximum results (default: 10)',
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
required: ['ticketId'],
|
|
680
|
+
},
|
|
681
|
+
},
|
|
682
|
+
// ========================================================================
|
|
683
|
+
// Git Integration Tools (Epic 10)
|
|
684
|
+
// ========================================================================
|
|
685
|
+
{
|
|
686
|
+
name: 'link_commit',
|
|
687
|
+
description: 'Associate a git commit with a ticket',
|
|
688
|
+
inputSchema: {
|
|
689
|
+
type: 'object',
|
|
690
|
+
properties: {
|
|
691
|
+
ticketId: {
|
|
692
|
+
type: 'string',
|
|
693
|
+
description: 'The ID of the ticket',
|
|
694
|
+
},
|
|
695
|
+
sha: {
|
|
696
|
+
type: 'string',
|
|
697
|
+
description: 'Full 40-character commit SHA',
|
|
698
|
+
},
|
|
699
|
+
message: {
|
|
700
|
+
type: 'string',
|
|
701
|
+
description: 'Commit message',
|
|
702
|
+
},
|
|
703
|
+
author: {
|
|
704
|
+
type: 'string',
|
|
705
|
+
description: 'Commit author',
|
|
706
|
+
},
|
|
707
|
+
repoUrl: {
|
|
708
|
+
type: 'string',
|
|
709
|
+
description: 'Repository URL for generating commit link',
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
required: ['ticketId', 'sha'],
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
name: 'link_pull_request',
|
|
717
|
+
description: 'Associate a pull request with a ticket',
|
|
718
|
+
inputSchema: {
|
|
719
|
+
type: 'object',
|
|
720
|
+
properties: {
|
|
721
|
+
ticketId: {
|
|
722
|
+
type: 'string',
|
|
723
|
+
description: 'The ID of the ticket',
|
|
724
|
+
},
|
|
725
|
+
prNumber: {
|
|
726
|
+
type: 'number',
|
|
727
|
+
description: 'PR number (provide this or prUrl)',
|
|
728
|
+
},
|
|
729
|
+
prUrl: {
|
|
730
|
+
type: 'string',
|
|
731
|
+
description: 'Full PR URL (GitHub, GitLab, or Bitbucket)',
|
|
732
|
+
},
|
|
733
|
+
title: {
|
|
734
|
+
type: 'string',
|
|
735
|
+
description: 'PR title',
|
|
736
|
+
},
|
|
737
|
+
author: {
|
|
738
|
+
type: 'string',
|
|
739
|
+
description: 'PR author',
|
|
740
|
+
},
|
|
741
|
+
repoUrl: {
|
|
742
|
+
type: 'string',
|
|
743
|
+
description: 'Repository URL (required if using prNumber)',
|
|
744
|
+
},
|
|
745
|
+
},
|
|
746
|
+
required: ['ticketId'],
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
name: 'get_ticket_commits',
|
|
751
|
+
description: 'Get all commits linked to a ticket',
|
|
752
|
+
inputSchema: {
|
|
753
|
+
type: 'object',
|
|
754
|
+
properties: {
|
|
755
|
+
ticketId: {
|
|
756
|
+
type: 'string',
|
|
757
|
+
description: 'The ID of the ticket',
|
|
758
|
+
},
|
|
759
|
+
limit: {
|
|
760
|
+
type: 'number',
|
|
761
|
+
description: 'Maximum results (default: 20)',
|
|
762
|
+
},
|
|
763
|
+
offset: {
|
|
764
|
+
type: 'number',
|
|
765
|
+
description: 'Pagination offset (default: 0)',
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
required: ['ticketId'],
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
name: 'get_ticket_prs',
|
|
773
|
+
description: 'Get all pull requests linked to a ticket',
|
|
774
|
+
inputSchema: {
|
|
775
|
+
type: 'object',
|
|
776
|
+
properties: {
|
|
777
|
+
ticketId: {
|
|
778
|
+
type: 'string',
|
|
779
|
+
description: 'The ID of the ticket',
|
|
780
|
+
},
|
|
781
|
+
limit: {
|
|
782
|
+
type: 'number',
|
|
783
|
+
description: 'Maximum results (default: 20)',
|
|
784
|
+
},
|
|
785
|
+
offset: {
|
|
786
|
+
type: 'number',
|
|
787
|
+
description: 'Pagination offset (default: 0)',
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
required: ['ticketId'],
|
|
791
|
+
},
|
|
792
|
+
},
|
|
793
|
+
// ========================================================================
|
|
794
|
+
// Creation Operations
|
|
795
|
+
// ========================================================================
|
|
796
|
+
{
|
|
797
|
+
name: 'create_specification',
|
|
798
|
+
description: 'Create a new specification with optional nested epics and tickets for bulk creation',
|
|
799
|
+
inputSchema: {
|
|
800
|
+
type: 'object',
|
|
801
|
+
properties: {
|
|
802
|
+
projectId: {
|
|
803
|
+
type: 'string',
|
|
804
|
+
description: 'The ID of the project to create the specification in',
|
|
805
|
+
},
|
|
806
|
+
title: {
|
|
807
|
+
type: 'string',
|
|
808
|
+
description: 'Specification title',
|
|
809
|
+
},
|
|
810
|
+
description: {
|
|
811
|
+
type: 'string',
|
|
812
|
+
description: 'Brief description (2-3 sentences)',
|
|
813
|
+
},
|
|
814
|
+
content: {
|
|
815
|
+
type: 'string',
|
|
816
|
+
description: 'Full markdown content',
|
|
817
|
+
},
|
|
818
|
+
specificationTypeId: {
|
|
819
|
+
type: 'string',
|
|
820
|
+
description: 'Specification type ID (uses default if not provided)',
|
|
821
|
+
},
|
|
822
|
+
background: {
|
|
823
|
+
type: 'string',
|
|
824
|
+
description: 'Problem context and why this exists',
|
|
825
|
+
},
|
|
826
|
+
scope: {
|
|
827
|
+
type: 'string',
|
|
828
|
+
description: "What's in/out of scope",
|
|
829
|
+
},
|
|
830
|
+
goals: {
|
|
831
|
+
type: 'array',
|
|
832
|
+
items: { type: 'string' },
|
|
833
|
+
description: 'Business/user/technical objectives',
|
|
834
|
+
},
|
|
835
|
+
requirements: {
|
|
836
|
+
type: 'array',
|
|
837
|
+
items: { type: 'string' },
|
|
838
|
+
description: 'Functional requirements',
|
|
839
|
+
},
|
|
840
|
+
acceptanceCriteria: {
|
|
841
|
+
type: 'array',
|
|
842
|
+
items: { type: 'string' },
|
|
843
|
+
description: 'Success criteria',
|
|
844
|
+
},
|
|
845
|
+
techStack: {
|
|
846
|
+
type: 'array',
|
|
847
|
+
items: { type: 'string' },
|
|
848
|
+
description: 'Technologies to use',
|
|
849
|
+
},
|
|
850
|
+
priority: {
|
|
851
|
+
type: 'string',
|
|
852
|
+
enum: ['high', 'medium', 'low'],
|
|
853
|
+
description: 'Priority level',
|
|
854
|
+
},
|
|
855
|
+
tags: {
|
|
856
|
+
type: 'array',
|
|
857
|
+
items: { type: 'string' },
|
|
858
|
+
description: 'Categorization tags',
|
|
859
|
+
},
|
|
860
|
+
estimatedHours: {
|
|
861
|
+
type: 'number',
|
|
862
|
+
description: 'Total estimated effort in hours',
|
|
863
|
+
},
|
|
864
|
+
epics: {
|
|
865
|
+
type: 'array',
|
|
866
|
+
description: 'Nested epics to create (bulk creation)',
|
|
867
|
+
items: {
|
|
868
|
+
type: 'object',
|
|
869
|
+
properties: {
|
|
870
|
+
title: { type: 'string' },
|
|
871
|
+
description: { type: 'string' },
|
|
872
|
+
objective: { type: 'string' },
|
|
873
|
+
tickets: {
|
|
874
|
+
type: 'array',
|
|
875
|
+
items: {
|
|
876
|
+
type: 'object',
|
|
877
|
+
properties: {
|
|
878
|
+
title: { type: 'string' },
|
|
879
|
+
description: { type: 'string' },
|
|
880
|
+
acceptanceCriteria: { type: 'array', items: { type: 'string' } },
|
|
881
|
+
estimatedHours: { type: 'number' },
|
|
882
|
+
},
|
|
883
|
+
required: ['title'],
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
required: ['title', 'description', 'objective'],
|
|
888
|
+
},
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
required: ['projectId', 'title'],
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
name: 'create_epic',
|
|
896
|
+
description: 'Create a new epic in a specification with optional nested tickets for bulk creation',
|
|
897
|
+
inputSchema: {
|
|
898
|
+
type: 'object',
|
|
899
|
+
properties: {
|
|
900
|
+
specificationId: {
|
|
901
|
+
type: 'string',
|
|
902
|
+
description: 'The ID of the specification',
|
|
903
|
+
},
|
|
904
|
+
title: {
|
|
905
|
+
type: 'string',
|
|
906
|
+
description: 'Epic title',
|
|
907
|
+
},
|
|
908
|
+
description: {
|
|
909
|
+
type: 'string',
|
|
910
|
+
description: 'Detailed description',
|
|
911
|
+
},
|
|
912
|
+
objective: {
|
|
913
|
+
type: 'string',
|
|
914
|
+
description: 'What this epic achieves',
|
|
915
|
+
},
|
|
916
|
+
epicNumber: {
|
|
917
|
+
type: 'number',
|
|
918
|
+
description: 'Epic number (auto-incremented if not provided)',
|
|
919
|
+
},
|
|
920
|
+
content: {
|
|
921
|
+
type: 'string',
|
|
922
|
+
description: 'Full epic content (markdown)',
|
|
923
|
+
},
|
|
924
|
+
scope: {
|
|
925
|
+
type: 'string',
|
|
926
|
+
description: "What's in/out of scope for this epic",
|
|
927
|
+
},
|
|
928
|
+
goals: {
|
|
929
|
+
type: 'array',
|
|
930
|
+
items: { type: 'string' },
|
|
931
|
+
description: 'Epic-specific goals',
|
|
932
|
+
},
|
|
933
|
+
acceptanceCriteria: {
|
|
934
|
+
type: 'array',
|
|
935
|
+
items: { type: 'string' },
|
|
936
|
+
description: 'Epic-level success criteria',
|
|
937
|
+
},
|
|
938
|
+
priority: {
|
|
939
|
+
type: 'string',
|
|
940
|
+
enum: ['high', 'medium', 'low'],
|
|
941
|
+
description: 'Priority level',
|
|
942
|
+
},
|
|
943
|
+
tags: {
|
|
944
|
+
type: 'array',
|
|
945
|
+
items: { type: 'string' },
|
|
946
|
+
description: 'Categorization tags',
|
|
947
|
+
},
|
|
948
|
+
estimatedHours: {
|
|
949
|
+
type: 'number',
|
|
950
|
+
description: 'Total estimated hours for epic',
|
|
951
|
+
},
|
|
952
|
+
tickets: {
|
|
953
|
+
type: 'array',
|
|
954
|
+
description: 'Nested tickets to create (bulk creation)',
|
|
955
|
+
items: {
|
|
956
|
+
type: 'object',
|
|
957
|
+
properties: {
|
|
958
|
+
title: { type: 'string' },
|
|
959
|
+
description: { type: 'string' },
|
|
960
|
+
acceptanceCriteria: { type: 'array', items: { type: 'string' } },
|
|
961
|
+
estimatedHours: { type: 'number' },
|
|
962
|
+
complexity: { type: 'string', enum: ['small', 'medium', 'large', 'xlarge'] },
|
|
963
|
+
},
|
|
964
|
+
required: ['title'],
|
|
965
|
+
},
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
required: ['specificationId', 'title', 'description', 'objective'],
|
|
969
|
+
},
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
name: 'create_ticket',
|
|
973
|
+
description: 'Create a new ticket in an epic',
|
|
974
|
+
inputSchema: {
|
|
975
|
+
type: 'object',
|
|
976
|
+
properties: {
|
|
977
|
+
epicId: {
|
|
978
|
+
type: 'string',
|
|
979
|
+
description: 'The ID of the epic',
|
|
980
|
+
},
|
|
981
|
+
title: {
|
|
982
|
+
type: 'string',
|
|
983
|
+
description: 'Ticket title',
|
|
984
|
+
},
|
|
985
|
+
ticketNumber: {
|
|
986
|
+
type: 'number',
|
|
987
|
+
description: 'Ticket number (auto-incremented if not provided)',
|
|
988
|
+
},
|
|
989
|
+
description: {
|
|
990
|
+
type: 'string',
|
|
991
|
+
description: 'Ticket description',
|
|
992
|
+
},
|
|
993
|
+
implementation: {
|
|
994
|
+
type: 'object',
|
|
995
|
+
description: 'Detailed implementation steps (JSON)',
|
|
996
|
+
},
|
|
997
|
+
technicalDetails: {
|
|
998
|
+
type: 'object',
|
|
999
|
+
description: 'Technical details like stack, endpoints (JSON)',
|
|
1000
|
+
},
|
|
1001
|
+
acceptanceCriteria: {
|
|
1002
|
+
type: 'array',
|
|
1003
|
+
items: { type: 'string' },
|
|
1004
|
+
description: 'Success criteria',
|
|
1005
|
+
},
|
|
1006
|
+
priority: {
|
|
1007
|
+
type: 'string',
|
|
1008
|
+
enum: ['high', 'medium', 'low'],
|
|
1009
|
+
description: 'Priority level',
|
|
1010
|
+
},
|
|
1011
|
+
complexity: {
|
|
1012
|
+
type: 'string',
|
|
1013
|
+
enum: ['small', 'medium', 'large', 'xlarge'],
|
|
1014
|
+
description: 'Size/complexity indicator',
|
|
1015
|
+
},
|
|
1016
|
+
tags: {
|
|
1017
|
+
type: 'array',
|
|
1018
|
+
items: { type: 'string' },
|
|
1019
|
+
description: 'Categorization tags',
|
|
1020
|
+
},
|
|
1021
|
+
estimatedHours: {
|
|
1022
|
+
type: 'number',
|
|
1023
|
+
description: 'Estimated effort in hours',
|
|
1024
|
+
},
|
|
1025
|
+
dependsOn: {
|
|
1026
|
+
type: 'array',
|
|
1027
|
+
items: { type: 'string' },
|
|
1028
|
+
description: 'Array of ticket IDs this ticket depends on',
|
|
1029
|
+
},
|
|
1030
|
+
},
|
|
1031
|
+
required: ['epicId', 'title'],
|
|
1032
|
+
},
|
|
1033
|
+
},
|
|
1034
|
+
{
|
|
1035
|
+
name: 'import_specification',
|
|
1036
|
+
description: 'Import a markdown file as a complete specification with AI-powered parsing. Parses epics from ## headers and tickets from ### headers.',
|
|
1037
|
+
inputSchema: {
|
|
1038
|
+
type: 'object',
|
|
1039
|
+
properties: {
|
|
1040
|
+
projectId: {
|
|
1041
|
+
type: 'string',
|
|
1042
|
+
description: 'The ID of the project to import into',
|
|
1043
|
+
},
|
|
1044
|
+
content: {
|
|
1045
|
+
type: 'string',
|
|
1046
|
+
description: 'Raw markdown content to parse and import',
|
|
1047
|
+
},
|
|
1048
|
+
title: {
|
|
1049
|
+
type: 'string',
|
|
1050
|
+
description: 'Override the parsed title (optional)',
|
|
1051
|
+
},
|
|
1052
|
+
specificationTypeId: {
|
|
1053
|
+
type: 'string',
|
|
1054
|
+
description: 'Specification type ID (uses default if not provided)',
|
|
1055
|
+
},
|
|
1056
|
+
},
|
|
1057
|
+
required: ['projectId', 'content'],
|
|
1058
|
+
},
|
|
1059
|
+
},
|
|
1060
|
+
];
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Retry configuration for transient failures
|
|
1064
|
+
*/
|
|
1065
|
+
const RETRY_CONFIG = {
|
|
1066
|
+
maxRetries: 3,
|
|
1067
|
+
initialDelayMs: 500,
|
|
1068
|
+
maxDelayMs: 5000,
|
|
1069
|
+
backoffMultiplier: 2,
|
|
1070
|
+
};
|
|
1071
|
+
/**
|
|
1072
|
+
* Transient error patterns that should trigger retry
|
|
1073
|
+
*/
|
|
1074
|
+
const TRANSIENT_ERROR_PATTERNS = [
|
|
1075
|
+
/ETIMEDOUT/i,
|
|
1076
|
+
/ECONNRESET/i,
|
|
1077
|
+
/ECONNREFUSED/i,
|
|
1078
|
+
/socket hang up/i,
|
|
1079
|
+
/network error/i,
|
|
1080
|
+
/too many requests/i,
|
|
1081
|
+
/rate limit/i,
|
|
1082
|
+
/5\d{2}/, // 5xx status codes
|
|
1083
|
+
];
|
|
1084
|
+
/**
|
|
1085
|
+
* Check if an error is transient and should be retried
|
|
1086
|
+
*/
|
|
1087
|
+
function isTransientError(error) {
|
|
1088
|
+
return TRANSIENT_ERROR_PATTERNS.some(pattern => pattern.test(error.message));
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Sleep for a given number of milliseconds
|
|
1092
|
+
*/
|
|
1093
|
+
function sleep(ms) {
|
|
1094
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Execute a function with retry logic for transient failures
|
|
1098
|
+
*/
|
|
1099
|
+
async function withRetry(fn, toolName, debug) {
|
|
1100
|
+
let lastError = null;
|
|
1101
|
+
let delay = RETRY_CONFIG.initialDelayMs;
|
|
1102
|
+
for (let attempt = 1; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
1103
|
+
try {
|
|
1104
|
+
return await fn();
|
|
1105
|
+
}
|
|
1106
|
+
catch (error) {
|
|
1107
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1108
|
+
if (attempt < RETRY_CONFIG.maxRetries && isTransientError(lastError)) {
|
|
1109
|
+
if (debug) {
|
|
1110
|
+
console.error(`[DEBUG] Tool ${toolName} attempt ${attempt} failed with transient error, retrying in ${delay}ms:`, lastError.message);
|
|
1111
|
+
}
|
|
1112
|
+
await sleep(delay);
|
|
1113
|
+
delay = Math.min(delay * RETRY_CONFIG.backoffMultiplier, RETRY_CONFIG.maxDelayMs);
|
|
1114
|
+
}
|
|
1115
|
+
else {
|
|
1116
|
+
break;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
throw lastError;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Create tool handlers that call the API with proper request formatting
|
|
1124
|
+
*
|
|
1125
|
+
* Each handler:
|
|
1126
|
+
* - Validates required arguments
|
|
1127
|
+
* - Formats the request for the API
|
|
1128
|
+
* - Parses and transforms the response
|
|
1129
|
+
* - Handles errors appropriately
|
|
1130
|
+
*
|
|
1131
|
+
* @param apiClient - The API client to use for making requests
|
|
1132
|
+
* @returns Record of tool handlers keyed by tool name
|
|
1133
|
+
*/
|
|
1134
|
+
export function createToolHandlers(apiClient) {
|
|
1135
|
+
return {
|
|
1136
|
+
// ========================================================================
|
|
1137
|
+
// Core Operations - Projects
|
|
1138
|
+
// ========================================================================
|
|
1139
|
+
list_projects: async (_client, _args) => {
|
|
1140
|
+
return await apiClient.call('list_projects', {});
|
|
1141
|
+
},
|
|
1142
|
+
get_project: async (_client, args) => {
|
|
1143
|
+
validateRequired(args, 'projectId');
|
|
1144
|
+
return await apiClient.call('get_project', {
|
|
1145
|
+
projectId: args.projectId,
|
|
1146
|
+
});
|
|
1147
|
+
},
|
|
1148
|
+
// ========================================================================
|
|
1149
|
+
// Core Operations - Specifications
|
|
1150
|
+
// ========================================================================
|
|
1151
|
+
list_specifications: async (_client, args) => {
|
|
1152
|
+
validateRequired(args, 'projectId');
|
|
1153
|
+
return await apiClient.call('list_specifications', {
|
|
1154
|
+
projectId: args.projectId,
|
|
1155
|
+
status: args.status,
|
|
1156
|
+
});
|
|
1157
|
+
},
|
|
1158
|
+
get_specification: async (_client, args) => {
|
|
1159
|
+
validateRequired(args, 'specificationId');
|
|
1160
|
+
return await apiClient.call('get_specification', {
|
|
1161
|
+
specificationId: args.specificationId,
|
|
1162
|
+
});
|
|
1163
|
+
},
|
|
1164
|
+
// ========================================================================
|
|
1165
|
+
// Core Operations - Epics
|
|
1166
|
+
// ========================================================================
|
|
1167
|
+
list_epics: async (_client, args) => {
|
|
1168
|
+
validateRequired(args, 'specificationId');
|
|
1169
|
+
return await apiClient.call('list_epics', {
|
|
1170
|
+
specificationId: args.specificationId,
|
|
1171
|
+
status: args.status,
|
|
1172
|
+
});
|
|
1173
|
+
},
|
|
1174
|
+
get_epic: async (_client, args) => {
|
|
1175
|
+
validateRequired(args, 'epicId');
|
|
1176
|
+
return await apiClient.call('get_epic', {
|
|
1177
|
+
epicId: args.epicId,
|
|
1178
|
+
});
|
|
1179
|
+
},
|
|
1180
|
+
// ========================================================================
|
|
1181
|
+
// Core Operations - Tickets
|
|
1182
|
+
// ========================================================================
|
|
1183
|
+
list_tickets: async (_client, args) => {
|
|
1184
|
+
validateRequired(args, 'epicId');
|
|
1185
|
+
return await apiClient.call('list_tickets', {
|
|
1186
|
+
epicId: args.epicId,
|
|
1187
|
+
status: args.status,
|
|
1188
|
+
});
|
|
1189
|
+
},
|
|
1190
|
+
get_ticket: async (_client, args) => {
|
|
1191
|
+
validateRequired(args, 'ticketId');
|
|
1192
|
+
return await apiClient.call('get_ticket', {
|
|
1193
|
+
ticketId: args.ticketId,
|
|
1194
|
+
});
|
|
1195
|
+
},
|
|
1196
|
+
// ========================================================================
|
|
1197
|
+
// Core Operations - Dependencies
|
|
1198
|
+
// ========================================================================
|
|
1199
|
+
get_ticket_dependencies: async (_client, args) => {
|
|
1200
|
+
validateRequired(args, 'ticketId');
|
|
1201
|
+
return await apiClient.call('get_ticket_dependencies', {
|
|
1202
|
+
ticketId: args.ticketId,
|
|
1203
|
+
});
|
|
1204
|
+
},
|
|
1205
|
+
// ========================================================================
|
|
1206
|
+
// Context & AI Tools
|
|
1207
|
+
// ========================================================================
|
|
1208
|
+
get_implementation_context: async (_client, args) => {
|
|
1209
|
+
validateRequired(args, 'ticketId');
|
|
1210
|
+
return await apiClient.call('get_implementation_context', {
|
|
1211
|
+
ticketId: args.ticketId,
|
|
1212
|
+
});
|
|
1213
|
+
},
|
|
1214
|
+
get_next_actionable_tickets: async (_client, args) => {
|
|
1215
|
+
validateRequired(args, 'specificationId');
|
|
1216
|
+
return await apiClient.call('get_next_actionable_tickets', {
|
|
1217
|
+
specificationId: args.specificationId,
|
|
1218
|
+
limit: args.limit ?? 5,
|
|
1219
|
+
});
|
|
1220
|
+
},
|
|
1221
|
+
get_blocked_tickets: async (_client, args) => {
|
|
1222
|
+
validateRequired(args, 'specificationId');
|
|
1223
|
+
return await apiClient.call('get_blocked_tickets', {
|
|
1224
|
+
specificationId: args.specificationId,
|
|
1225
|
+
});
|
|
1226
|
+
},
|
|
1227
|
+
get_critical_path: async (_client, args) => {
|
|
1228
|
+
validateRequired(args, 'specificationId');
|
|
1229
|
+
return await apiClient.call('get_critical_path', {
|
|
1230
|
+
specificationId: args.specificationId,
|
|
1231
|
+
});
|
|
1232
|
+
},
|
|
1233
|
+
get_patterns: async (_client, args) => {
|
|
1234
|
+
validateRequired(args, 'specificationId');
|
|
1235
|
+
return await apiClient.call('get_patterns', {
|
|
1236
|
+
specificationId: args.specificationId,
|
|
1237
|
+
});
|
|
1238
|
+
},
|
|
1239
|
+
// ========================================================================
|
|
1240
|
+
// Workflow & Tracking
|
|
1241
|
+
// ========================================================================
|
|
1242
|
+
start_work_session: async (_client, args) => {
|
|
1243
|
+
validateRequired(args, 'ticketId');
|
|
1244
|
+
return await apiClient.call('start_work_session', {
|
|
1245
|
+
ticketId: args.ticketId,
|
|
1246
|
+
});
|
|
1247
|
+
},
|
|
1248
|
+
complete_work_session: async (_client, args) => {
|
|
1249
|
+
validateRequired(args, 'ticketId', 'summary');
|
|
1250
|
+
return await apiClient.call('complete_work_session', {
|
|
1251
|
+
ticketId: args.ticketId,
|
|
1252
|
+
summary: args.summary,
|
|
1253
|
+
filesModified: args.filesModified,
|
|
1254
|
+
filesCreated: args.filesCreated,
|
|
1255
|
+
});
|
|
1256
|
+
},
|
|
1257
|
+
report_progress: async (_client, args) => {
|
|
1258
|
+
validateRequired(args, 'ticketId', 'progress');
|
|
1259
|
+
validateRange(args.progress, 'progress', 0, 100);
|
|
1260
|
+
return await apiClient.call('report_progress', {
|
|
1261
|
+
ticketId: args.ticketId,
|
|
1262
|
+
progress: args.progress,
|
|
1263
|
+
message: args.message,
|
|
1264
|
+
});
|
|
1265
|
+
},
|
|
1266
|
+
get_work_summary: async (_client, args) => {
|
|
1267
|
+
validateRequired(args, 'scope');
|
|
1268
|
+
return await apiClient.call('get_work_summary', {
|
|
1269
|
+
scope: args.scope,
|
|
1270
|
+
scopeId: args.scopeId,
|
|
1271
|
+
startDate: args.startDate,
|
|
1272
|
+
endDate: args.endDate,
|
|
1273
|
+
});
|
|
1274
|
+
},
|
|
1275
|
+
// ========================================================================
|
|
1276
|
+
// Testing Tools
|
|
1277
|
+
// ========================================================================
|
|
1278
|
+
report_test_results: async (_client, args) => {
|
|
1279
|
+
validateRequired(args, 'ticketId', 'passed', 'testType');
|
|
1280
|
+
return await apiClient.call('report_test_results', {
|
|
1281
|
+
ticketId: args.ticketId,
|
|
1282
|
+
passed: args.passed,
|
|
1283
|
+
testType: args.testType,
|
|
1284
|
+
summary: args.summary,
|
|
1285
|
+
output: args.output,
|
|
1286
|
+
});
|
|
1287
|
+
},
|
|
1288
|
+
get_ticket_test_status: async (_client, args) => {
|
|
1289
|
+
validateRequired(args, 'ticketId');
|
|
1290
|
+
return await apiClient.call('get_ticket_test_status', {
|
|
1291
|
+
ticketId: args.ticketId,
|
|
1292
|
+
});
|
|
1293
|
+
},
|
|
1294
|
+
validate_ticket_completion: async (_client, args) => {
|
|
1295
|
+
validateRequired(args, 'ticketId');
|
|
1296
|
+
return await apiClient.call('validate_ticket_completion', {
|
|
1297
|
+
ticketId: args.ticketId,
|
|
1298
|
+
});
|
|
1299
|
+
},
|
|
1300
|
+
// ========================================================================
|
|
1301
|
+
// Discovery Tools
|
|
1302
|
+
// ========================================================================
|
|
1303
|
+
report_discovery: async (_client, args) => {
|
|
1304
|
+
validateRequired(args, 'ticketId', 'type', 'title');
|
|
1305
|
+
return await apiClient.call('report_discovery', {
|
|
1306
|
+
ticketId: args.ticketId,
|
|
1307
|
+
type: args.type,
|
|
1308
|
+
title: args.title,
|
|
1309
|
+
description: args.description,
|
|
1310
|
+
priority: args.priority,
|
|
1311
|
+
});
|
|
1312
|
+
},
|
|
1313
|
+
get_pending_discoveries: async (_client, args) => {
|
|
1314
|
+
return await apiClient.call('get_pending_discoveries', {
|
|
1315
|
+
specificationId: args.specificationId,
|
|
1316
|
+
projectId: args.projectId,
|
|
1317
|
+
});
|
|
1318
|
+
},
|
|
1319
|
+
resolve_discovery: async (_client, args) => {
|
|
1320
|
+
validateRequired(args, 'discoveryId', 'resolution');
|
|
1321
|
+
return await apiClient.call('resolve_discovery', {
|
|
1322
|
+
discoveryId: args.discoveryId,
|
|
1323
|
+
resolution: args.resolution,
|
|
1324
|
+
createdTicketId: args.createdTicketId,
|
|
1325
|
+
notes: args.notes,
|
|
1326
|
+
});
|
|
1327
|
+
},
|
|
1328
|
+
// ========================================================================
|
|
1329
|
+
// Status & Analytics Tools
|
|
1330
|
+
// ========================================================================
|
|
1331
|
+
get_specification_status: async (_client, args) => {
|
|
1332
|
+
validateRequired(args, 'specificationId');
|
|
1333
|
+
return await apiClient.call('get_specification_status', {
|
|
1334
|
+
specificationId: args.specificationId,
|
|
1335
|
+
});
|
|
1336
|
+
},
|
|
1337
|
+
get_epic_status: async (_client, args) => {
|
|
1338
|
+
validateRequired(args, 'epicId');
|
|
1339
|
+
return await apiClient.call('get_epic_status', {
|
|
1340
|
+
epicId: args.epicId,
|
|
1341
|
+
});
|
|
1342
|
+
},
|
|
1343
|
+
get_implementation_summary: async (_client, args) => {
|
|
1344
|
+
return await apiClient.call('get_implementation_summary', {
|
|
1345
|
+
projectId: args.projectId,
|
|
1346
|
+
});
|
|
1347
|
+
},
|
|
1348
|
+
get_time_report: async (_client, args) => {
|
|
1349
|
+
validateRequired(args, 'scope', 'scopeId');
|
|
1350
|
+
return await apiClient.call('get_time_report', {
|
|
1351
|
+
scope: args.scope,
|
|
1352
|
+
scopeId: args.scopeId,
|
|
1353
|
+
});
|
|
1354
|
+
},
|
|
1355
|
+
get_blockers_report: async (_client, args) => {
|
|
1356
|
+
return await apiClient.call('get_blockers_report', {
|
|
1357
|
+
specificationId: args.specificationId,
|
|
1358
|
+
projectId: args.projectId,
|
|
1359
|
+
});
|
|
1360
|
+
},
|
|
1361
|
+
// ========================================================================
|
|
1362
|
+
// Search Tools
|
|
1363
|
+
// ========================================================================
|
|
1364
|
+
search_tickets: async (_client, args) => {
|
|
1365
|
+
validateRequired(args, 'query');
|
|
1366
|
+
return await apiClient.call('search_tickets', {
|
|
1367
|
+
query: args.query,
|
|
1368
|
+
specificationId: args.specificationId,
|
|
1369
|
+
projectId: args.projectId,
|
|
1370
|
+
limit: args.limit ?? 20,
|
|
1371
|
+
offset: args.offset ?? 0,
|
|
1372
|
+
});
|
|
1373
|
+
},
|
|
1374
|
+
find_tickets_by_file: async (_client, args) => {
|
|
1375
|
+
validateRequired(args, 'filePath');
|
|
1376
|
+
return await apiClient.call('find_tickets_by_file', {
|
|
1377
|
+
filePath: args.filePath,
|
|
1378
|
+
specificationId: args.specificationId,
|
|
1379
|
+
projectId: args.projectId,
|
|
1380
|
+
});
|
|
1381
|
+
},
|
|
1382
|
+
find_tickets_by_tag: async (_client, args) => {
|
|
1383
|
+
validateRequired(args, 'tags');
|
|
1384
|
+
return await apiClient.call('find_tickets_by_tag', {
|
|
1385
|
+
tags: args.tags,
|
|
1386
|
+
matchAll: args.matchAll ?? false,
|
|
1387
|
+
specificationId: args.specificationId,
|
|
1388
|
+
projectId: args.projectId,
|
|
1389
|
+
});
|
|
1390
|
+
},
|
|
1391
|
+
find_related_tickets: async (_client, args) => {
|
|
1392
|
+
validateRequired(args, 'ticketId');
|
|
1393
|
+
return await apiClient.call('find_related_tickets', {
|
|
1394
|
+
ticketId: args.ticketId,
|
|
1395
|
+
limit: args.limit ?? 10,
|
|
1396
|
+
});
|
|
1397
|
+
},
|
|
1398
|
+
// ========================================================================
|
|
1399
|
+
// Git Integration Tools
|
|
1400
|
+
// ========================================================================
|
|
1401
|
+
link_commit: async (_client, args) => {
|
|
1402
|
+
validateRequired(args, 'ticketId', 'sha');
|
|
1403
|
+
validateSha(args.sha);
|
|
1404
|
+
return await apiClient.call('link_commit', {
|
|
1405
|
+
ticketId: args.ticketId,
|
|
1406
|
+
sha: args.sha,
|
|
1407
|
+
message: args.message,
|
|
1408
|
+
author: args.author,
|
|
1409
|
+
repoUrl: args.repoUrl,
|
|
1410
|
+
});
|
|
1411
|
+
},
|
|
1412
|
+
link_pull_request: async (_client, args) => {
|
|
1413
|
+
validateRequired(args, 'ticketId');
|
|
1414
|
+
if (!args.prNumber && !args.prUrl) {
|
|
1415
|
+
throw new Error('Either prNumber or prUrl must be provided');
|
|
1416
|
+
}
|
|
1417
|
+
return await apiClient.call('link_pull_request', {
|
|
1418
|
+
ticketId: args.ticketId,
|
|
1419
|
+
prNumber: args.prNumber,
|
|
1420
|
+
prUrl: args.prUrl,
|
|
1421
|
+
title: args.title,
|
|
1422
|
+
author: args.author,
|
|
1423
|
+
repoUrl: args.repoUrl,
|
|
1424
|
+
});
|
|
1425
|
+
},
|
|
1426
|
+
get_ticket_commits: async (_client, args) => {
|
|
1427
|
+
validateRequired(args, 'ticketId');
|
|
1428
|
+
return await apiClient.call('get_ticket_commits', {
|
|
1429
|
+
ticketId: args.ticketId,
|
|
1430
|
+
limit: args.limit ?? 20,
|
|
1431
|
+
offset: args.offset ?? 0,
|
|
1432
|
+
});
|
|
1433
|
+
},
|
|
1434
|
+
get_ticket_prs: async (_client, args) => {
|
|
1435
|
+
validateRequired(args, 'ticketId');
|
|
1436
|
+
return await apiClient.call('get_ticket_prs', {
|
|
1437
|
+
ticketId: args.ticketId,
|
|
1438
|
+
limit: args.limit ?? 20,
|
|
1439
|
+
offset: args.offset ?? 0,
|
|
1440
|
+
});
|
|
1441
|
+
},
|
|
1442
|
+
// ========================================================================
|
|
1443
|
+
// Creation Operations
|
|
1444
|
+
// ========================================================================
|
|
1445
|
+
create_specification: async (_client, args) => {
|
|
1446
|
+
validateRequired(args, 'projectId', 'title');
|
|
1447
|
+
return await apiClient.call('create_specification', {
|
|
1448
|
+
projectId: args.projectId,
|
|
1449
|
+
title: args.title,
|
|
1450
|
+
description: args.description,
|
|
1451
|
+
content: args.content,
|
|
1452
|
+
specificationTypeId: args.specificationTypeId,
|
|
1453
|
+
background: args.background,
|
|
1454
|
+
scope: args.scope,
|
|
1455
|
+
goals: args.goals,
|
|
1456
|
+
requirements: args.requirements,
|
|
1457
|
+
acceptanceCriteria: args.acceptanceCriteria,
|
|
1458
|
+
techStack: args.techStack,
|
|
1459
|
+
priority: args.priority,
|
|
1460
|
+
tags: args.tags,
|
|
1461
|
+
estimatedHours: args.estimatedHours,
|
|
1462
|
+
epics: args.epics,
|
|
1463
|
+
});
|
|
1464
|
+
},
|
|
1465
|
+
create_epic: async (_client, args) => {
|
|
1466
|
+
validateRequired(args, 'specificationId', 'title', 'description', 'objective');
|
|
1467
|
+
return await apiClient.call('create_epic', {
|
|
1468
|
+
specificationId: args.specificationId,
|
|
1469
|
+
title: args.title,
|
|
1470
|
+
description: args.description,
|
|
1471
|
+
objective: args.objective,
|
|
1472
|
+
epicNumber: args.epicNumber,
|
|
1473
|
+
content: args.content,
|
|
1474
|
+
scope: args.scope,
|
|
1475
|
+
goals: args.goals,
|
|
1476
|
+
acceptanceCriteria: args.acceptanceCriteria,
|
|
1477
|
+
priority: args.priority,
|
|
1478
|
+
tags: args.tags,
|
|
1479
|
+
estimatedHours: args.estimatedHours,
|
|
1480
|
+
order: args.order,
|
|
1481
|
+
tickets: args.tickets,
|
|
1482
|
+
});
|
|
1483
|
+
},
|
|
1484
|
+
create_ticket: async (_client, args) => {
|
|
1485
|
+
validateRequired(args, 'epicId', 'title');
|
|
1486
|
+
return await apiClient.call('create_ticket', {
|
|
1487
|
+
epicId: args.epicId,
|
|
1488
|
+
title: args.title,
|
|
1489
|
+
ticketNumber: args.ticketNumber,
|
|
1490
|
+
description: args.description,
|
|
1491
|
+
implementation: args.implementation,
|
|
1492
|
+
technicalDetails: args.technicalDetails,
|
|
1493
|
+
acceptanceCriteria: args.acceptanceCriteria,
|
|
1494
|
+
priority: args.priority,
|
|
1495
|
+
complexity: args.complexity,
|
|
1496
|
+
tags: args.tags,
|
|
1497
|
+
estimatedHours: args.estimatedHours,
|
|
1498
|
+
order: args.order,
|
|
1499
|
+
dependsOn: args.dependsOn,
|
|
1500
|
+
});
|
|
1501
|
+
},
|
|
1502
|
+
import_specification: async (_client, args) => {
|
|
1503
|
+
validateRequired(args, 'projectId', 'content');
|
|
1504
|
+
return await apiClient.call('import_specification', {
|
|
1505
|
+
projectId: args.projectId,
|
|
1506
|
+
content: args.content,
|
|
1507
|
+
title: args.title,
|
|
1508
|
+
specificationTypeId: args.specificationTypeId,
|
|
1509
|
+
});
|
|
1510
|
+
},
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Validate that required arguments are present
|
|
1515
|
+
* @throws Error if any required argument is missing
|
|
1516
|
+
*/
|
|
1517
|
+
function validateRequired(args, ...required) {
|
|
1518
|
+
for (const field of required) {
|
|
1519
|
+
if (args[field] === undefined || args[field] === null || args[field] === '') {
|
|
1520
|
+
throw new Error(`Missing required argument: ${field}`);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Validate that a number is within a range
|
|
1526
|
+
* @throws Error if the value is out of range
|
|
1527
|
+
*/
|
|
1528
|
+
function validateRange(value, field, min, max) {
|
|
1529
|
+
if (typeof value !== 'number' || value < min || value > max) {
|
|
1530
|
+
throw new Error(`${field} must be between ${min} and ${max}`);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Validate SHA format (40 hexadecimal characters)
|
|
1535
|
+
* @throws Error if the SHA is invalid
|
|
1536
|
+
*/
|
|
1537
|
+
function validateSha(sha) {
|
|
1538
|
+
if (!/^[0-9a-f]{40}$/i.test(sha)) {
|
|
1539
|
+
throw new Error('sha must be a 40-character hexadecimal string');
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
// Create a cached instance of tool handlers
|
|
1543
|
+
let cachedHandlers = null;
|
|
1544
|
+
/**
|
|
1545
|
+
* Handle a tool call by routing to the appropriate handler
|
|
1546
|
+
*
|
|
1547
|
+
* @param apiClient - The API client to use for making requests
|
|
1548
|
+
* @param toolName - Name of the tool being called
|
|
1549
|
+
* @param args - Arguments passed to the tool
|
|
1550
|
+
* @param debug - Whether to enable debug logging
|
|
1551
|
+
* @returns Promise resolving to the tool result
|
|
1552
|
+
*/
|
|
1553
|
+
export async function handleToolCall(apiClient, toolName, args, debug = false) {
|
|
1554
|
+
const startTime = Date.now();
|
|
1555
|
+
// Create handlers if not cached
|
|
1556
|
+
if (!cachedHandlers) {
|
|
1557
|
+
cachedHandlers = createToolHandlers(apiClient);
|
|
1558
|
+
}
|
|
1559
|
+
// Get the handler for this tool
|
|
1560
|
+
const handler = cachedHandlers[toolName];
|
|
1561
|
+
if (!handler) {
|
|
1562
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
1563
|
+
}
|
|
1564
|
+
if (debug) {
|
|
1565
|
+
console.error(`[DEBUG] Calling tool: ${toolName}`, {
|
|
1566
|
+
args: JSON.stringify(args),
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
try {
|
|
1570
|
+
// Run comprehensive validation before executing
|
|
1571
|
+
validateToolArgs(toolName, args);
|
|
1572
|
+
// Execute with retry logic
|
|
1573
|
+
const result = await withRetry(() => handler(apiClient, args), toolName, debug);
|
|
1574
|
+
const duration = Date.now() - startTime;
|
|
1575
|
+
if (debug) {
|
|
1576
|
+
console.error(`[DEBUG] Tool ${toolName} completed in ${duration}ms`);
|
|
1577
|
+
}
|
|
1578
|
+
return result;
|
|
1579
|
+
}
|
|
1580
|
+
catch (error) {
|
|
1581
|
+
const duration = Date.now() - startTime;
|
|
1582
|
+
// Transform error for proper handling
|
|
1583
|
+
const transformedError = transformError(error);
|
|
1584
|
+
const message = transformedError.message;
|
|
1585
|
+
if (debug) {
|
|
1586
|
+
console.error(`[DEBUG] Tool ${toolName} failed after ${duration}ms:`, message);
|
|
1587
|
+
if (transformedError instanceof ValidationError) {
|
|
1588
|
+
console.error(`[DEBUG] Validation field: ${transformedError.field}, code: ${transformedError.code}`);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
// Rethrow with context
|
|
1592
|
+
if (transformedError instanceof ValidationError) {
|
|
1593
|
+
throw transformedError;
|
|
1594
|
+
}
|
|
1595
|
+
if (transformedError instanceof ApiError) {
|
|
1596
|
+
throw transformedError;
|
|
1597
|
+
}
|
|
1598
|
+
throw new Error(`Tool ${toolName} failed: ${message}`);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Handle a tool call and return MCP-formatted response
|
|
1603
|
+
* This wraps handleToolCall with proper error formatting for MCP protocol
|
|
1604
|
+
*
|
|
1605
|
+
* @param apiClient - The API client to use for making requests
|
|
1606
|
+
* @param toolName - Name of the tool being called
|
|
1607
|
+
* @param args - Arguments passed to the tool
|
|
1608
|
+
* @param debug - Whether to enable debug logging
|
|
1609
|
+
* @returns Promise resolving to either the result or an MCP error response
|
|
1610
|
+
*/
|
|
1611
|
+
export async function handleToolCallSafe(apiClient, toolName, args, debug = false) {
|
|
1612
|
+
try {
|
|
1613
|
+
return await handleToolCall(apiClient, toolName, args, debug);
|
|
1614
|
+
}
|
|
1615
|
+
catch (error) {
|
|
1616
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1617
|
+
return formatMCPError(err);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
// Re-export validation utilities for use by other modules
|
|
1621
|
+
export { ValidationError, ApiError, validateToolArgs, formatMCPError, transformError, } from '../validation/index.js';
|
|
1622
|
+
//# sourceMappingURL=index.js.map
|