wolfpack-mcp 1.0.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/dist/index.js ADDED
@@ -0,0 +1,939 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { z } from 'zod';
6
+ import { WolfpackClient } from './client.js';
7
+ import { validateConfig, config } from './config.js';
8
+ // Work Item schemas
9
+ const ListWorkItemsSchema = z.object({
10
+ team_id: z.number().optional().describe('Team ID to filter work items'),
11
+ status: z.string().optional().describe('Filter by status'),
12
+ limit: z.number().optional().describe('Maximum number of items to return'),
13
+ offset: z.number().optional().describe('Number of items to skip'),
14
+ });
15
+ const GetWorkItemSchema = z.object({
16
+ work_item_id: z.string().describe('The ID (UUID) or refId (number) of the work item'),
17
+ team_id: z.number().optional().describe('Team ID (required when looking up by refId)'),
18
+ });
19
+ const UpdateWorkProgressSchema = z.object({
20
+ work_item_id: z.string().describe('The ID of the work item'),
21
+ description: z.string().describe('Updated description/notes for the work item'),
22
+ });
23
+ const UpdateWorkItemStatusSchema = z.object({
24
+ work_item_id: z.string().describe('The ID of the work item'),
25
+ status: z.string().describe('New status'),
26
+ });
27
+ // Radar Item (Initiative/Roadmap) schemas
28
+ const ListRadarItemsSchema = z.object({
29
+ team_id: z.number().optional().describe('Team ID to filter radar items'),
30
+ stage: z
31
+ .enum(['pending', 'now', 'next', 'later', 'completed'])
32
+ .optional()
33
+ .describe('Filter by stage'),
34
+ limit: z.number().optional().describe('Maximum number of items to return'),
35
+ offset: z.number().optional().describe('Number of items to skip'),
36
+ });
37
+ const GetRadarItemSchema = z.object({
38
+ item_id: z.string().describe('The ID (UUID) or refId (number) of the radar item'),
39
+ team_id: z.number().optional().describe('Team ID (required when looking up by refId)'),
40
+ });
41
+ // Issue schemas
42
+ const ListIssuesSchema = z.object({
43
+ team_id: z.number().optional().describe('Team ID to filter issues'),
44
+ status: z
45
+ .enum(['open', 'in-progress', 'resolved', 'closed'])
46
+ .optional()
47
+ .describe('Filter by status'),
48
+ severity: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Filter by severity'),
49
+ type: z
50
+ .enum(['bug', 'feature-request', 'task', 'improvement', 'question'])
51
+ .optional()
52
+ .describe('Filter by type'),
53
+ assigned_to_id: z
54
+ .string()
55
+ .optional()
56
+ .describe('Filter by assignee ID (use "unassigned" for unassigned, "me" for self)'),
57
+ limit: z.number().optional().describe('Maximum number of items to return'),
58
+ offset: z.number().optional().describe('Number of items to skip'),
59
+ });
60
+ const GetIssueSchema = z.object({
61
+ issue_id: z.string().describe('The ID (UUID) or refId (number) of the issue'),
62
+ team_id: z.number().optional().describe('Team ID (required when looking up by refId)'),
63
+ });
64
+ // Create schemas
65
+ const CreateWorkItemSchema = z.object({
66
+ title: z.string().describe('Title of the work item'),
67
+ description: z.string().optional().describe('Description/notes for the work item (markdown)'),
68
+ status: z.string().optional().describe('Initial status'),
69
+ priority: z.number().optional().describe('Priority level (0-4, higher is more important)'),
70
+ leading_user_id: z.string().optional().describe('User ID to assign as leading user'),
71
+ });
72
+ const CreateIssueSchema = z.object({
73
+ title: z.string().describe('Title of the issue'),
74
+ description: z.string().optional().describe('Description of the issue'),
75
+ severity: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Severity level'),
76
+ type: z
77
+ .enum(['bug', 'feature-request', 'task', 'improvement', 'question'])
78
+ .optional()
79
+ .describe('Issue type'),
80
+ assigned_to_id: z.string().optional().describe('User ID to assign the issue to'),
81
+ environment: z.string().optional().describe('Environment where the issue occurred'),
82
+ affected_version: z.string().optional().describe('Version affected by the issue'),
83
+ reproducible: z.boolean().optional().describe('Whether the issue is reproducible'),
84
+ steps_to_reproduce: z.string().optional().describe('Steps to reproduce the issue'),
85
+ expected_behavior: z.string().optional().describe('Expected behavior'),
86
+ actual_behavior: z.string().optional().describe('Actual behavior observed'),
87
+ });
88
+ const UpdateIssueSchema = z.object({
89
+ issue_id: z.string().describe('The ID (UUID) of the issue to update'),
90
+ title: z.string().optional().describe('Updated title'),
91
+ description: z.string().optional().describe('Updated description'),
92
+ status: z
93
+ .enum(['open', 'in-progress', 'resolved', 'closed'])
94
+ .optional()
95
+ .describe('Updated status'),
96
+ severity: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Updated severity'),
97
+ assigned_to_id: z.string().optional().describe('Updated assignee'),
98
+ closing_note: z.string().optional().describe('Closing note (when closing the issue)'),
99
+ });
100
+ // Wiki Page schemas
101
+ const ListWikiPagesSchema = z.object({
102
+ limit: z.number().optional().describe('Maximum number of pages to return'),
103
+ offset: z.number().optional().describe('Number of pages to skip'),
104
+ });
105
+ const GetWikiPageSchema = z.object({
106
+ page_id: z.string().describe('The page UUID or slug'),
107
+ });
108
+ const CreateWikiPageSchema = z.object({
109
+ slug: z.string().describe('URL slug for the page (without team prefix)'),
110
+ title: z.string().describe('Page title'),
111
+ content: z.string().describe('Page content (markdown)'),
112
+ });
113
+ const UpdateWikiPageSchema = z.object({
114
+ page_id: z.string().describe('The page UUID'),
115
+ title: z.string().optional().describe('Updated title'),
116
+ content: z.string().optional().describe('Updated content (markdown)'),
117
+ });
118
+ // Journal Entry schemas
119
+ const ListJournalEntriesSchema = z.object({
120
+ limit: z.number().optional().describe('Maximum number of entries to return'),
121
+ offset: z.number().optional().describe('Number of entries to skip'),
122
+ });
123
+ const GetJournalEntrySchema = z.object({
124
+ entry_id: z.string().describe('The entry UUID or refId'),
125
+ team_id: z.number().optional().describe('Team ID (required when looking up by refId)'),
126
+ });
127
+ const CreateJournalEntrySchema = z.object({
128
+ title: z.string().describe('Entry title'),
129
+ content: z.string().describe('Entry content (markdown)'),
130
+ date: z.string().optional().describe('Entry date (ISO format, defaults to now)'),
131
+ });
132
+ const UpdateJournalEntrySchema = z.object({
133
+ entry_id: z.string().describe('The entry UUID'),
134
+ title: z.string().optional().describe('Updated title'),
135
+ content: z.string().optional().describe('Updated content (markdown)'),
136
+ });
137
+ // Comment schemas
138
+ const CreateWorkItemCommentSchema = z.object({
139
+ work_item_id: z.string().describe('The work item UUID'),
140
+ content: z.string().describe('Comment content (markdown)'),
141
+ });
142
+ const CreateIssueCommentSchema = z.object({
143
+ issue_id: z.string().describe('The issue UUID'),
144
+ content: z.string().describe('Comment content (markdown)'),
145
+ });
146
+ class WolfpackMCPServer {
147
+ server;
148
+ client;
149
+ constructor() {
150
+ this.client = new WolfpackClient();
151
+ this.server = new Server({
152
+ name: 'wolfpack',
153
+ version: '1.0.0',
154
+ }, {
155
+ capabilities: {
156
+ tools: {},
157
+ },
158
+ });
159
+ this.setupHandlers();
160
+ }
161
+ setupHandlers() {
162
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
163
+ tools: [
164
+ // Team tools
165
+ {
166
+ name: 'list_teams',
167
+ description: 'List all teams you have access to. IMPORTANT: Call this first if you need to know the team_id for other operations. Returns team IDs, slugs, and names. Single-team users have their team auto-selected; multi-team users must specify team_id in other tool calls.',
168
+ inputSchema: {
169
+ type: 'object',
170
+ properties: {},
171
+ },
172
+ },
173
+ // Work Item tools
174
+ {
175
+ name: 'list_work_items',
176
+ description: 'List work items (tasks/tickets) assigned to you. Use when user asks about "my work", "my tasks", "what am I working on", "assigned to me", or "backlog". Status mapping: "new" = backlog/todo, "doing" = in progress/active, "review" = pending review, "completed" = done/finished.',
177
+ inputSchema: {
178
+ type: 'object',
179
+ properties: {
180
+ team_id: {
181
+ type: 'number',
182
+ description: 'Team ID (required for multi-team users, use list_teams to get IDs)',
183
+ },
184
+ status: {
185
+ type: 'string',
186
+ description: 'Filter: "new" (backlog/todo), "doing" (in progress), "review", "completed" (done), "blocked"',
187
+ },
188
+ limit: { type: 'number', description: 'Maximum number of items to return' },
189
+ offset: { type: 'number', description: 'Number of items to skip' },
190
+ },
191
+ },
192
+ },
193
+ {
194
+ name: 'get_work_item',
195
+ description: 'Get a specific work item/task by ID or reference number. Returns full details including description (markdown notes). Call this before updating to see current content.',
196
+ inputSchema: {
197
+ type: 'object',
198
+ properties: {
199
+ work_item_id: {
200
+ type: 'string',
201
+ description: 'The ID (UUID) or refId (number) of the work item',
202
+ },
203
+ team_id: {
204
+ type: 'number',
205
+ description: 'Team ID (required when looking up by refId)',
206
+ },
207
+ },
208
+ required: ['work_item_id'],
209
+ },
210
+ },
211
+ {
212
+ name: 'update_work_progress',
213
+ description: 'Update work item description/notes with your progress. WORKFLOW: 1) get_work_item to read current notes 2) Modify the markdown description with new findings/progress 3) Call this with the complete updated description. The full description replaces the existing one.',
214
+ inputSchema: {
215
+ type: 'object',
216
+ properties: {
217
+ work_item_id: { type: 'string', description: 'The ID of the work item' },
218
+ description: {
219
+ type: 'string',
220
+ description: 'Updated description/notes for the work item',
221
+ },
222
+ },
223
+ required: ['work_item_id', 'description'],
224
+ },
225
+ },
226
+ {
227
+ name: 'update_work_item_status',
228
+ description: 'Change work item status. WORKFLOW: Move to "doing" when you start work, or "review" when ready for review. Can also set to "completed", "blocked", etc.',
229
+ inputSchema: {
230
+ type: 'object',
231
+ properties: {
232
+ work_item_id: {
233
+ type: 'string',
234
+ description: 'The ID of the work item',
235
+ },
236
+ status: {
237
+ type: 'string',
238
+ description: 'New status',
239
+ },
240
+ },
241
+ required: ['work_item_id', 'status'],
242
+ },
243
+ },
244
+ // Radar Item (Initiative/Roadmap) tools
245
+ {
246
+ name: 'list_radar_items',
247
+ description: 'List radar items - these are initiatives, epics, or roadmap items that group related work items. Use when user asks about "roadmap", "initiatives", "epics", "projects", "what\'s planned", or "upcoming work". Stage: "now" (current sprint), "next" (upcoming), "later" (future), "pending" (not started), "completed".',
248
+ inputSchema: {
249
+ type: 'object',
250
+ properties: {
251
+ team_id: { type: 'number', description: 'Team ID (use list_teams to get IDs)' },
252
+ stage: {
253
+ type: 'string',
254
+ enum: ['pending', 'now', 'next', 'later', 'completed'],
255
+ description: 'Filter: "now" (current), "next" (upcoming), "later" (future), "pending", "completed"',
256
+ },
257
+ limit: { type: 'number', description: 'Maximum number of items to return' },
258
+ offset: { type: 'number', description: 'Number of items to skip' },
259
+ },
260
+ },
261
+ },
262
+ {
263
+ name: 'get_radar_item',
264
+ description: 'Get a specific radar item (initiative) by ID or refId, including its work items and participants.',
265
+ inputSchema: {
266
+ type: 'object',
267
+ properties: {
268
+ item_id: {
269
+ type: 'string',
270
+ description: 'The ID (UUID) or refId (number) of the radar item',
271
+ },
272
+ team_id: {
273
+ type: 'number',
274
+ description: 'Team ID (required when looking up by refId)',
275
+ },
276
+ },
277
+ required: ['item_id'],
278
+ },
279
+ },
280
+ // Issue tools
281
+ {
282
+ name: 'list_issues',
283
+ description: 'List issues (bugs, tickets, problems, feature requests). Use when user asks about "bugs", "issues", "tickets", "problems", "defects", or "feature requests". Can filter by status, severity, and type.',
284
+ inputSchema: {
285
+ type: 'object',
286
+ properties: {
287
+ team_id: {
288
+ type: 'number',
289
+ description: 'Team ID (required for multi-team users, use list_teams to get IDs)',
290
+ },
291
+ status: {
292
+ type: 'string',
293
+ enum: ['open', 'in-progress', 'resolved', 'closed'],
294
+ description: 'Filter: "open" (new/active), "in-progress", "resolved" (fixed), "closed"',
295
+ },
296
+ severity: {
297
+ type: 'string',
298
+ enum: ['low', 'medium', 'high', 'critical'],
299
+ description: 'Filter by severity/priority: "critical", "high", "medium", "low"',
300
+ },
301
+ type: {
302
+ type: 'string',
303
+ enum: ['bug', 'feature-request', 'task', 'improvement', 'question'],
304
+ description: 'Filter: "bug" (defect), "feature-request", "task", "improvement", "question"',
305
+ },
306
+ assigned_to_id: {
307
+ type: 'string',
308
+ description: 'Filter by assignee ID (use "unassigned" for unassigned, "me" for self)',
309
+ },
310
+ limit: { type: 'number', description: 'Maximum number of items to return' },
311
+ offset: { type: 'number', description: 'Number of items to skip' },
312
+ },
313
+ },
314
+ },
315
+ {
316
+ name: 'get_issue',
317
+ description: 'Get a specific issue by ID or refId, including comment and attachment counts.',
318
+ inputSchema: {
319
+ type: 'object',
320
+ properties: {
321
+ issue_id: {
322
+ type: 'string',
323
+ description: 'The ID (UUID) or refId (number) of the issue',
324
+ },
325
+ team_id: {
326
+ type: 'number',
327
+ description: 'Team ID (required when looking up by refId)',
328
+ },
329
+ },
330
+ required: ['issue_id'],
331
+ },
332
+ },
333
+ // Create/Update tools
334
+ {
335
+ name: 'create_work_item',
336
+ description: 'Create a new work item in your current team (auto-selected for single-team users, or use list_teams first for multi-team). Requires mcp:work_items:create permission.',
337
+ inputSchema: {
338
+ type: 'object',
339
+ properties: {
340
+ title: { type: 'string', description: 'Title of the work item' },
341
+ description: {
342
+ type: 'string',
343
+ description: 'Description/notes for the work item (markdown)',
344
+ },
345
+ status: {
346
+ type: 'string',
347
+ description: 'Initial status (e.g., new, doing, review, completed - defaults to new)',
348
+ },
349
+ priority: {
350
+ type: 'number',
351
+ description: 'Priority level (0-4, higher is more important)',
352
+ },
353
+ leading_user_id: {
354
+ type: 'string',
355
+ description: 'User ID to assign as leading user',
356
+ },
357
+ },
358
+ required: ['title'],
359
+ },
360
+ },
361
+ {
362
+ name: 'create_issue',
363
+ description: 'Create a new issue in your current team (auto-selected for single-team users, or use list_teams first for multi-team). Requires mcp:issues:create permission.',
364
+ inputSchema: {
365
+ type: 'object',
366
+ properties: {
367
+ title: { type: 'string', description: 'Title of the issue' },
368
+ description: { type: 'string', description: 'Description of the issue' },
369
+ severity: {
370
+ type: 'string',
371
+ enum: ['low', 'medium', 'high', 'critical'],
372
+ description: 'Severity level',
373
+ },
374
+ type: {
375
+ type: 'string',
376
+ enum: ['bug', 'feature-request', 'task', 'improvement', 'question'],
377
+ description: 'Issue type',
378
+ },
379
+ assigned_to_id: { type: 'string', description: 'User ID to assign the issue to' },
380
+ environment: { type: 'string', description: 'Environment where the issue occurred' },
381
+ affected_version: { type: 'string', description: 'Version affected by the issue' },
382
+ reproducible: { type: 'boolean', description: 'Whether the issue is reproducible' },
383
+ steps_to_reproduce: { type: 'string', description: 'Steps to reproduce the issue' },
384
+ expected_behavior: { type: 'string', description: 'Expected behavior' },
385
+ actual_behavior: { type: 'string', description: 'Actual behavior observed' },
386
+ },
387
+ required: ['title'],
388
+ },
389
+ },
390
+ {
391
+ name: 'update_issue',
392
+ description: 'Update an existing issue. Requires mcp:issues:update permission.',
393
+ inputSchema: {
394
+ type: 'object',
395
+ properties: {
396
+ issue_id: { type: 'string', description: 'The ID (UUID) of the issue to update' },
397
+ title: { type: 'string', description: 'Updated title' },
398
+ description: { type: 'string', description: 'Updated description' },
399
+ status: {
400
+ type: 'string',
401
+ enum: ['open', 'in-progress', 'resolved', 'closed'],
402
+ description: 'Updated status',
403
+ },
404
+ severity: {
405
+ type: 'string',
406
+ enum: ['low', 'medium', 'high', 'critical'],
407
+ description: 'Updated severity',
408
+ },
409
+ assigned_to_id: { type: 'string', description: 'Updated assignee' },
410
+ closing_note: {
411
+ type: 'string',
412
+ description: 'Closing note (when closing the issue)',
413
+ },
414
+ },
415
+ required: ['issue_id'],
416
+ },
417
+ },
418
+ // Wiki Page tools
419
+ {
420
+ name: 'list_wiki_pages',
421
+ description: 'List wiki pages (documentation, docs, knowledge base). Use when user asks about "docs", "documentation", "wiki", "knowledge base", or "how-to guides".',
422
+ inputSchema: {
423
+ type: 'object',
424
+ properties: {
425
+ limit: { type: 'number', description: 'Maximum number of pages to return' },
426
+ offset: { type: 'number', description: 'Number of pages to skip' },
427
+ },
428
+ },
429
+ },
430
+ {
431
+ name: 'get_wiki_page',
432
+ description: 'Get a specific wiki/documentation page by ID or slug (URL path). Returns full content in markdown.',
433
+ inputSchema: {
434
+ type: 'object',
435
+ properties: {
436
+ page_id: {
437
+ type: 'string',
438
+ description: 'The page UUID or slug (URL path like "getting-started")',
439
+ },
440
+ },
441
+ required: ['page_id'],
442
+ },
443
+ },
444
+ {
445
+ name: 'create_wiki_page',
446
+ description: 'Create a new wiki/documentation page. Use when user wants to "add docs", "create documentation", or "write a guide".',
447
+ inputSchema: {
448
+ type: 'object',
449
+ properties: {
450
+ slug: { type: 'string', description: 'URL slug for the page (without team prefix)' },
451
+ title: { type: 'string', description: 'Page title' },
452
+ content: { type: 'string', description: 'Page content (markdown)' },
453
+ },
454
+ required: ['slug', 'title', 'content'],
455
+ },
456
+ },
457
+ {
458
+ name: 'update_wiki_page',
459
+ description: 'Update an existing wiki page. Requires mcp:wiki:update permission.',
460
+ inputSchema: {
461
+ type: 'object',
462
+ properties: {
463
+ page_id: { type: 'string', description: 'The page UUID' },
464
+ title: { type: 'string', description: 'Updated title' },
465
+ content: { type: 'string', description: 'Updated content (markdown)' },
466
+ },
467
+ required: ['page_id'],
468
+ },
469
+ },
470
+ // Journal Entry tools
471
+ {
472
+ name: 'list_journal_entries',
473
+ description: 'List journal entries (daily logs, standup notes, progress updates). Use when user asks about "journal", "daily log", "standup notes", "progress updates", or "what did I work on".',
474
+ inputSchema: {
475
+ type: 'object',
476
+ properties: {
477
+ limit: { type: 'number', description: 'Maximum number of entries to return' },
478
+ offset: { type: 'number', description: 'Number of entries to skip' },
479
+ },
480
+ },
481
+ },
482
+ {
483
+ name: 'get_journal_entry',
484
+ description: 'Get a specific journal/log entry by ID or reference number. Returns full content.',
485
+ inputSchema: {
486
+ type: 'object',
487
+ properties: {
488
+ entry_id: { type: 'string', description: 'The entry UUID or refId' },
489
+ team_id: {
490
+ type: 'number',
491
+ description: 'Team ID (required when looking up by refId)',
492
+ },
493
+ },
494
+ required: ['entry_id'],
495
+ },
496
+ },
497
+ {
498
+ name: 'create_journal_entry',
499
+ description: 'Create a new journal entry (daily log, standup note, progress update). Use when user wants to "log progress", "write a standup", "add a journal entry", or "record what I did".',
500
+ inputSchema: {
501
+ type: 'object',
502
+ properties: {
503
+ title: { type: 'string', description: 'Entry title' },
504
+ content: { type: 'string', description: 'Entry content (markdown)' },
505
+ date: { type: 'string', description: 'Entry date (ISO format, defaults to now)' },
506
+ },
507
+ required: ['title', 'content'],
508
+ },
509
+ },
510
+ {
511
+ name: 'update_journal_entry',
512
+ description: 'Update an existing journal entry. Requires mcp:journal:update permission.',
513
+ inputSchema: {
514
+ type: 'object',
515
+ properties: {
516
+ entry_id: { type: 'string', description: 'The entry UUID' },
517
+ title: { type: 'string', description: 'Updated title' },
518
+ content: { type: 'string', description: 'Updated content (markdown)' },
519
+ },
520
+ required: ['entry_id'],
521
+ },
522
+ },
523
+ // Comment tools
524
+ {
525
+ name: 'create_work_item_comment',
526
+ description: 'Add a comment to a work item. Requires mcp:comments:create permission.',
527
+ inputSchema: {
528
+ type: 'object',
529
+ properties: {
530
+ work_item_id: { type: 'string', description: 'The work item UUID' },
531
+ content: { type: 'string', description: 'Comment content (markdown)' },
532
+ },
533
+ required: ['work_item_id', 'content'],
534
+ },
535
+ },
536
+ {
537
+ name: 'create_issue_comment',
538
+ description: 'Add a comment to an issue. Requires mcp:comments:create permission.',
539
+ inputSchema: {
540
+ type: 'object',
541
+ properties: {
542
+ issue_id: { type: 'string', description: 'The issue UUID' },
543
+ content: { type: 'string', description: 'Comment content (markdown)' },
544
+ },
545
+ required: ['issue_id', 'content'],
546
+ },
547
+ },
548
+ ],
549
+ }));
550
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
551
+ const { name, arguments: args } = request.params;
552
+ try {
553
+ switch (name) {
554
+ // Team handlers
555
+ case 'list_teams': {
556
+ const teams = await this.client.listTeams();
557
+ const currentTeamId = this.client.getTeamId();
558
+ return {
559
+ content: [
560
+ {
561
+ type: 'text',
562
+ text: JSON.stringify({
563
+ teams,
564
+ currentTeamId,
565
+ hint: teams.length === 1
566
+ ? 'Only one team available - it will be used automatically.'
567
+ : 'Multiple teams available. Use team_id parameter in other tools to specify which team to use.',
568
+ }, null, 2),
569
+ },
570
+ ],
571
+ };
572
+ }
573
+ // Work Item handlers
574
+ case 'list_work_items': {
575
+ const parsed = ListWorkItemsSchema.parse(args);
576
+ const workItems = await this.client.listWorkItems({
577
+ teamId: parsed.team_id || this.client.getTeamId() || undefined,
578
+ status: parsed.status,
579
+ limit: parsed.limit,
580
+ offset: parsed.offset,
581
+ });
582
+ return {
583
+ content: [{ type: 'text', text: JSON.stringify(workItems, null, 2) }],
584
+ };
585
+ }
586
+ case 'get_work_item': {
587
+ const parsed = GetWorkItemSchema.parse(args);
588
+ const workItem = await this.client.getWorkItem(parsed.work_item_id, parsed.team_id || this.client.getTeamId() || undefined);
589
+ if (workItem) {
590
+ return {
591
+ content: [{ type: 'text', text: JSON.stringify(workItem, null, 2) }],
592
+ };
593
+ }
594
+ return {
595
+ content: [{ type: 'text', text: 'Work item not found or not assigned to you' }],
596
+ };
597
+ }
598
+ case 'update_work_progress': {
599
+ const parsed = UpdateWorkProgressSchema.parse(args);
600
+ const workItem = await this.client.updateWorkProgress(parsed.work_item_id, parsed.description);
601
+ if (workItem) {
602
+ return {
603
+ content: [
604
+ {
605
+ type: 'text',
606
+ text: `Updated description on: ${workItem.title}\n\n${JSON.stringify(workItem, null, 2)}`,
607
+ },
608
+ ],
609
+ };
610
+ }
611
+ return {
612
+ content: [{ type: 'text', text: 'Work item not found or not assigned to you' }],
613
+ };
614
+ }
615
+ case 'update_work_item_status': {
616
+ const parsed = UpdateWorkItemStatusSchema.parse(args);
617
+ const workItem = await this.client.updateWorkItemStatus(parsed.work_item_id, parsed.status);
618
+ if (workItem) {
619
+ return {
620
+ content: [
621
+ {
622
+ type: 'text',
623
+ text: `Updated status of "${workItem.title}" to ${parsed.status}\n\n${JSON.stringify(workItem, null, 2)}`,
624
+ },
625
+ ],
626
+ };
627
+ }
628
+ return {
629
+ content: [{ type: 'text', text: 'Work item not found or not assigned to you' }],
630
+ };
631
+ }
632
+ // Radar Item handlers
633
+ case 'list_radar_items': {
634
+ const parsed = ListRadarItemsSchema.parse(args);
635
+ const radarItems = await this.client.listRadarItems({
636
+ teamId: parsed.team_id,
637
+ stage: parsed.stage,
638
+ limit: parsed.limit,
639
+ offset: parsed.offset,
640
+ });
641
+ return {
642
+ content: [{ type: 'text', text: JSON.stringify(radarItems, null, 2) }],
643
+ };
644
+ }
645
+ case 'get_radar_item': {
646
+ const parsed = GetRadarItemSchema.parse(args);
647
+ const radarItem = await this.client.getRadarItem(parsed.item_id, parsed.team_id);
648
+ if (radarItem) {
649
+ return {
650
+ content: [{ type: 'text', text: JSON.stringify(radarItem, null, 2) }],
651
+ };
652
+ }
653
+ return {
654
+ content: [{ type: 'text', text: 'Radar item not found' }],
655
+ };
656
+ }
657
+ // Issue handlers
658
+ case 'list_issues': {
659
+ const parsed = ListIssuesSchema.parse(args);
660
+ const issues = await this.client.listIssues({
661
+ teamId: parsed.team_id,
662
+ status: parsed.status,
663
+ severity: parsed.severity,
664
+ type: parsed.type,
665
+ assignedToId: parsed.assigned_to_id,
666
+ limit: parsed.limit,
667
+ offset: parsed.offset,
668
+ });
669
+ return {
670
+ content: [{ type: 'text', text: JSON.stringify(issues, null, 2) }],
671
+ };
672
+ }
673
+ case 'get_issue': {
674
+ const parsed = GetIssueSchema.parse(args);
675
+ const issue = await this.client.getIssue(parsed.issue_id, parsed.team_id);
676
+ if (issue) {
677
+ return {
678
+ content: [{ type: 'text', text: JSON.stringify(issue, null, 2) }],
679
+ };
680
+ }
681
+ return {
682
+ content: [{ type: 'text', text: 'Issue not found' }],
683
+ };
684
+ }
685
+ // Create/Update handlers
686
+ case 'create_work_item': {
687
+ const parsed = CreateWorkItemSchema.parse(args);
688
+ const workItem = await this.client.createWorkItem({
689
+ title: parsed.title,
690
+ description: parsed.description,
691
+ status: parsed.status,
692
+ priority: parsed.priority,
693
+ leadingUserId: parsed.leading_user_id,
694
+ });
695
+ return {
696
+ content: [
697
+ {
698
+ type: 'text',
699
+ text: `Created work item #${workItem.refId}: ${workItem.title}\n\n${JSON.stringify(workItem, null, 2)}`,
700
+ },
701
+ ],
702
+ };
703
+ }
704
+ case 'create_issue': {
705
+ const parsed = CreateIssueSchema.parse(args);
706
+ const issue = await this.client.createIssue({
707
+ title: parsed.title,
708
+ description: parsed.description,
709
+ severity: parsed.severity,
710
+ type: parsed.type,
711
+ assignedToId: parsed.assigned_to_id,
712
+ environment: parsed.environment,
713
+ affectedVersion: parsed.affected_version,
714
+ reproducible: parsed.reproducible,
715
+ stepsToReproduce: parsed.steps_to_reproduce,
716
+ expectedBehavior: parsed.expected_behavior,
717
+ actualBehavior: parsed.actual_behavior,
718
+ });
719
+ return {
720
+ content: [
721
+ {
722
+ type: 'text',
723
+ text: `Created issue #${issue.refId}: ${issue.title}\n\n${JSON.stringify(issue, null, 2)}`,
724
+ },
725
+ ],
726
+ };
727
+ }
728
+ case 'update_issue': {
729
+ const parsed = UpdateIssueSchema.parse(args);
730
+ const issue = await this.client.updateIssue(parsed.issue_id, {
731
+ title: parsed.title,
732
+ description: parsed.description,
733
+ status: parsed.status,
734
+ severity: parsed.severity,
735
+ assignedToId: parsed.assigned_to_id,
736
+ closingNote: parsed.closing_note,
737
+ });
738
+ return {
739
+ content: [
740
+ {
741
+ type: 'text',
742
+ text: `Updated issue #${issue.refId}: ${issue.title}\n\n${JSON.stringify(issue, null, 2)}`,
743
+ },
744
+ ],
745
+ };
746
+ }
747
+ // Wiki Page handlers
748
+ case 'list_wiki_pages': {
749
+ const parsed = ListWikiPagesSchema.parse(args);
750
+ const pages = await this.client.listWikiPages({
751
+ limit: parsed.limit,
752
+ offset: parsed.offset,
753
+ });
754
+ return {
755
+ content: [{ type: 'text', text: JSON.stringify(pages, null, 2) }],
756
+ };
757
+ }
758
+ case 'get_wiki_page': {
759
+ const parsed = GetWikiPageSchema.parse(args);
760
+ const page = await this.client.getWikiPage(parsed.page_id);
761
+ if (page) {
762
+ return {
763
+ content: [{ type: 'text', text: JSON.stringify(page, null, 2) }],
764
+ };
765
+ }
766
+ return {
767
+ content: [{ type: 'text', text: 'Wiki page not found' }],
768
+ };
769
+ }
770
+ case 'create_wiki_page': {
771
+ const parsed = CreateWikiPageSchema.parse(args);
772
+ const page = await this.client.createWikiPage({
773
+ slug: parsed.slug,
774
+ title: parsed.title,
775
+ content: parsed.content,
776
+ });
777
+ return {
778
+ content: [
779
+ {
780
+ type: 'text',
781
+ text: `Created wiki page: ${page.title}\n\n${JSON.stringify(page, null, 2)}`,
782
+ },
783
+ ],
784
+ };
785
+ }
786
+ case 'update_wiki_page': {
787
+ const parsed = UpdateWikiPageSchema.parse(args);
788
+ const page = await this.client.updateWikiPage(parsed.page_id, {
789
+ title: parsed.title,
790
+ content: parsed.content,
791
+ });
792
+ return {
793
+ content: [
794
+ {
795
+ type: 'text',
796
+ text: `Updated wiki page: ${page.title}\n\n${JSON.stringify(page, null, 2)}`,
797
+ },
798
+ ],
799
+ };
800
+ }
801
+ // Journal Entry handlers
802
+ case 'list_journal_entries': {
803
+ const parsed = ListJournalEntriesSchema.parse(args);
804
+ const entries = await this.client.listJournalEntries({
805
+ limit: parsed.limit,
806
+ offset: parsed.offset,
807
+ });
808
+ return {
809
+ content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }],
810
+ };
811
+ }
812
+ case 'get_journal_entry': {
813
+ const parsed = GetJournalEntrySchema.parse(args);
814
+ const entry = await this.client.getJournalEntry(parsed.entry_id, parsed.team_id);
815
+ if (entry) {
816
+ return {
817
+ content: [{ type: 'text', text: JSON.stringify(entry, null, 2) }],
818
+ };
819
+ }
820
+ return {
821
+ content: [{ type: 'text', text: 'Journal entry not found' }],
822
+ };
823
+ }
824
+ case 'create_journal_entry': {
825
+ const parsed = CreateJournalEntrySchema.parse(args);
826
+ const entry = await this.client.createJournalEntry({
827
+ title: parsed.title,
828
+ content: parsed.content,
829
+ date: parsed.date,
830
+ });
831
+ return {
832
+ content: [
833
+ {
834
+ type: 'text',
835
+ text: `Created journal entry #${entry.refId}: ${entry.title}\n\n${JSON.stringify(entry, null, 2)}`,
836
+ },
837
+ ],
838
+ };
839
+ }
840
+ case 'update_journal_entry': {
841
+ const parsed = UpdateJournalEntrySchema.parse(args);
842
+ const entry = await this.client.updateJournalEntry(parsed.entry_id, {
843
+ title: parsed.title,
844
+ content: parsed.content,
845
+ });
846
+ return {
847
+ content: [
848
+ {
849
+ type: 'text',
850
+ text: `Updated journal entry #${entry.refId}: ${entry.title}\n\n${JSON.stringify(entry, null, 2)}`,
851
+ },
852
+ ],
853
+ };
854
+ }
855
+ // Comment handlers
856
+ case 'create_work_item_comment': {
857
+ const parsed = CreateWorkItemCommentSchema.parse(args);
858
+ const comment = await this.client.createWorkItemComment(parsed.work_item_id, {
859
+ content: parsed.content,
860
+ });
861
+ return {
862
+ content: [
863
+ {
864
+ type: 'text',
865
+ text: `Added comment to work item\n\n${JSON.stringify(comment, null, 2)}`,
866
+ },
867
+ ],
868
+ };
869
+ }
870
+ case 'create_issue_comment': {
871
+ const parsed = CreateIssueCommentSchema.parse(args);
872
+ const comment = await this.client.createIssueComment(parsed.issue_id, {
873
+ content: parsed.content,
874
+ });
875
+ return {
876
+ content: [
877
+ {
878
+ type: 'text',
879
+ text: `Added comment to issue\n\n${JSON.stringify(comment, null, 2)}`,
880
+ },
881
+ ],
882
+ };
883
+ }
884
+ default:
885
+ throw new Error(`Unknown tool: ${name}`);
886
+ }
887
+ }
888
+ catch (error) {
889
+ const errorMessage = error instanceof Error ? error.message : String(error);
890
+ return {
891
+ content: [{ type: 'text', text: `Error: ${errorMessage}` }],
892
+ isError: true,
893
+ };
894
+ }
895
+ });
896
+ }
897
+ async start() {
898
+ // Resolve team on startup
899
+ if (config.teamSlug) {
900
+ // Explicit team slug configured - use it
901
+ try {
902
+ const teamId = await this.client.resolveTeamSlug(config.teamSlug);
903
+ console.error(`Resolved team "${config.teamSlug}" to ID: ${teamId}`);
904
+ }
905
+ catch (error) {
906
+ console.error(`Failed to resolve team slug: ${error instanceof Error ? error.message : String(error)}`);
907
+ process.exit(1);
908
+ }
909
+ }
910
+ else {
911
+ // No team slug configured - try to auto-detect
912
+ try {
913
+ const teams = await this.client.listTeams();
914
+ if (teams.length === 1) {
915
+ // Auto-select the only team
916
+ this.client.setTeamId(teams[0].id);
917
+ console.error(`Auto-selected team: ${teams[0].name} (${teams[0].slug})`);
918
+ }
919
+ else if (teams.length === 0) {
920
+ console.error('Warning: No teams found for this API key');
921
+ }
922
+ else {
923
+ console.error(`Multiple teams available (${teams.length}). Use list_teams tool to see them, then specify team_id in tool calls.`);
924
+ }
925
+ }
926
+ catch (error) {
927
+ console.error(`Warning: Could not fetch teams: ${error instanceof Error ? error.message : String(error)}`);
928
+ // Don't exit - let the user use list_teams tool manually
929
+ }
930
+ }
931
+ const transport = new StdioServerTransport();
932
+ await this.server.connect(transport);
933
+ console.error('Wolfpack MCP Server v1.0.0 started');
934
+ }
935
+ }
936
+ // Validate configuration before starting
937
+ validateConfig();
938
+ const server = new WolfpackMCPServer();
939
+ server.start().catch(console.error);