wolfpack-mcp 1.0.41 → 1.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/client.js +11 -37
  2. package/dist/index.js +107 -168
  3. package/package.json +1 -1
package/dist/client.js CHANGED
@@ -175,42 +175,9 @@ export class WolfpackClient {
175
175
  throw error;
176
176
  }
177
177
  }
178
- async updateWorkItemStatus(workItemId, status, teamSlug) {
178
+ async updateWorkItem(workItemId, fields, teamSlug) {
179
179
  try {
180
- return await this.api.put(this.withTeamSlug(`/work-items/${workItemId}/status`, teamSlug), { status });
181
- }
182
- catch (error) {
183
- if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
184
- return null;
185
- }
186
- throw error;
187
- }
188
- }
189
- async updateWorkItemAssignee(workItemId, data, teamSlug) {
190
- try {
191
- return await this.api.put(this.withTeamSlug(`/work-items/${workItemId}/assignee`, teamSlug), data);
192
- }
193
- catch (error) {
194
- if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
195
- return null;
196
- }
197
- throw error;
198
- }
199
- }
200
- async updateWorkItemTitle(workItemId, title, teamSlug) {
201
- try {
202
- return await this.api.put(this.withTeamSlug(`/work-items/${workItemId}/title`, teamSlug), { title });
203
- }
204
- catch (error) {
205
- if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
206
- return null;
207
- }
208
- throw error;
209
- }
210
- }
211
- async updateWorkItemInitiative(workItemId, radarItemId, teamSlug) {
212
- try {
213
- return await this.api.put(this.withTeamSlug(`/work-items/${workItemId}/initiative`, teamSlug), { radarItemId });
180
+ return await this.api.patch(this.withTeamSlug(`/work-items/${workItemId}`, teamSlug), fields);
214
181
  }
215
182
  catch (error) {
216
183
  if (error && typeof error === 'object' && 'status' in error && error.status === 404) {
@@ -402,7 +369,7 @@ export class WolfpackClient {
402
369
  return this.api.post('/wiki-pages', { ...rest, teamSlug });
403
370
  }
404
371
  async updateWikiPage(pageId, data, teamSlug) {
405
- return this.api.patch(this.withTeamSlug(`/wiki-pages/${pageId}`, teamSlug), data);
372
+ return this.api.patch(this.withTeamSlug(`/wiki-pages/${encodeURIComponent(pageId)}`, teamSlug), data);
406
373
  }
407
374
  // Journal Entry methods
408
375
  async listJournalEntries(options) {
@@ -451,7 +418,7 @@ export class WolfpackClient {
451
418
  return this.api.post('/journal-entries', { ...rest, teamSlug });
452
419
  }
453
420
  async updateJournalEntry(entryId, data, teamSlug) {
454
- return this.api.patch(this.withTeamSlug(`/journal-entries/${entryId}`, teamSlug), data);
421
+ return this.api.patch(this.withTeamSlug(`/journal-entries/${encodeURIComponent(entryId)}`, teamSlug), data);
455
422
  }
456
423
  // Comment methods
457
424
  async listWorkItemComments(workItemId, teamSlug) {
@@ -480,6 +447,13 @@ export class WolfpackClient {
480
447
  const { buffer, contentType } = await this.api.getBuffer(`/images/${team}/${filename}`);
481
448
  return { base64: buffer.toString('base64'), mimeType: contentType };
482
449
  }
450
+ /**
451
+ * Download an issue attachment image and return raw base64 + mimeType.
452
+ */
453
+ async downloadIssueAttachment(team, refId, attachmentId) {
454
+ const { buffer, contentType } = await this.api.getBuffer(`/issues/${team}/${refId}/attachments/${attachmentId}`);
455
+ return { base64: buffer.toString('base64'), mimeType: contentType };
456
+ }
483
457
  // Skill methods (progressive disclosure)
484
458
  async listSkills() {
485
459
  return this.api.get('/skills');
package/dist/index.js CHANGED
@@ -112,39 +112,31 @@ const VALID_STATUSES = [
112
112
  'closed',
113
113
  'archived',
114
114
  ];
115
- const UpdateWorkItemStatusSchema = z.object({
116
- work_item_id: z.string().describe('The ID of the work item'),
117
- status: z.enum(VALID_STATUSES).describe('New status'),
118
- project_slug: z
119
- .string()
120
- .optional()
121
- .describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
122
- });
123
- const UpdateWorkItemAssigneeSchema = z.object({
115
+ const UpdateWorkItemSchema = z.object({
124
116
  work_item_id: z.string().describe('The ID of the work item'),
117
+ title: z.string().optional().describe('New title for the work item'),
118
+ status: z.enum(VALID_STATUSES).optional().describe('New status'),
125
119
  leading_user_id: z
126
120
  .string()
127
121
  .nullable()
122
+ .optional()
128
123
  .describe('User ID to assign as leading user, or null to unassign'),
129
- project_slug: z
124
+ radar_item_id: z
130
125
  .string()
126
+ .nullable()
131
127
  .optional()
132
- .describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
133
- });
134
- const UpdateWorkItemTitleSchema = z.object({
135
- work_item_id: z.string().describe('The ID of the work item'),
136
- title: z.string().describe('New title for the work item'),
137
- project_slug: z
128
+ .describe('Radar/initiative item ID to link to, or null to unlink'),
129
+ category_id: z
138
130
  .string()
131
+ .nullable()
139
132
  .optional()
140
- .describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
141
- });
142
- const UpdateWorkItemInitiativeSchema = z.object({
143
- work_item_id: z.string().describe('The ID of the work item'),
144
- radar_item_id: z
145
- .string()
133
+ .describe('Category ID to set, or null to remove the category'),
134
+ priority: z.coerce.number().optional().describe('Priority level (0-4, higher is more important)'),
135
+ size: z
136
+ .enum(['S', 'M', 'L'])
146
137
  .nullable()
147
- .describe('Radar/initiative item ID to link to, or null to unlink'),
138
+ .optional()
139
+ .describe('Size estimate: "S" (small), "M" (medium), "L" (large), or null to remove'),
148
140
  project_slug: z
149
141
  .string()
150
142
  .optional()
@@ -612,16 +604,13 @@ class WolfpackMCPServer {
612
604
  },
613
605
  },
614
606
  {
615
- name: 'update_work_item_status',
616
- description: 'Change work item status. ' +
617
- 'REQUIRED WORKFLOW when working on items: ' +
618
- '1) For "new" items: move to "doing" when starting work. ' +
619
- '2) For "pending" (backlog) items: use pull_work_item first (claims and sets to "new"), then move to "doing". ' +
620
- '3) For items in "blocked", "ready", "completed", or "closed": move to "new" first, then to "doing". ' +
621
- '4) When work is complete: move to "review" (NOT "completed" - that is only for deployed work) and add a completion comment summarizing what was done. ' +
622
- '5) If work cannot be done: move to "blocked" and add a detailed comment explaining the blocker, what was attempted, and what is needed to unblock. ' +
623
- 'Statuses: "pending" (backlog), "new" (to do), "doing" (in progress), "review" (work done, pending review), ' +
624
- '"ready" (reviewed, awaiting deployment), "blocked", "completed" (deployed).',
607
+ name: 'update_work_item',
608
+ description: 'Update one or more fields on a work item. Only provide the fields you want to change. ' +
609
+ 'Supports: title, status, leading_user_id (assignee), radar_item_id (initiative), ' +
610
+ 'category_id, priority (0-4), and size (S/M/L). ' +
611
+ 'STATUS WORKFLOW: "pending" (backlog) "new" (to do) → "doing" (in progress) "review" (work done) "ready" (awaiting deployment) → "completed" (deployed). ' +
612
+ 'Use "blocked" when work cannot proceed. For "pending" items use pull_work_item first. ' +
613
+ 'When moving to "review", add a completion comment. When moving to "blocked", add a comment explaining the blocker.',
625
614
  inputSchema: {
626
615
  type: 'object',
627
616
  properties: {
@@ -629,6 +618,10 @@ class WolfpackMCPServer {
629
618
  type: 'string',
630
619
  description: 'The ID of the work item',
631
620
  },
621
+ title: {
622
+ type: 'string',
623
+ description: 'New title for the work item',
624
+ },
632
625
  status: {
633
626
  type: 'string',
634
627
  enum: [
@@ -642,84 +635,35 @@ class WolfpackMCPServer {
642
635
  'closed',
643
636
  'archived',
644
637
  ],
645
- description: 'New status: "pending" (backlog), "new" (to do), "doing" (in progress), "review" (work done), "ready" (awaiting deployment), "blocked", "completed" (deployed), "closed", "archived"',
646
- },
647
- project_slug: {
648
- type: 'string',
649
- description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
650
- },
651
- },
652
- required: ['work_item_id', 'status'],
653
- },
654
- },
655
- {
656
- name: 'update_work_item_assignee',
657
- description: 'Change the assignee (leading user) on a work item. ' +
658
- 'Use this to assign work to yourself or another project member, or to unassign by passing null. ' +
659
- 'Not applicable for personal projects (single user).',
660
- inputSchema: {
661
- type: 'object',
662
- properties: {
663
- work_item_id: {
664
- type: 'string',
665
- description: 'The ID of the work item',
638
+ description: 'New status',
666
639
  },
667
640
  leading_user_id: {
668
641
  type: ['string', 'null'],
669
642
  description: 'User ID to assign as leading user, or null to unassign',
670
643
  },
671
- project_slug: {
672
- type: 'string',
673
- description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
674
- },
675
- },
676
- required: ['work_item_id', 'leading_user_id'],
677
- },
678
- },
679
- {
680
- name: 'update_work_item_title',
681
- description: 'Change the title of a work item. ' +
682
- 'Use this to rename or update the title of an existing work item.',
683
- inputSchema: {
684
- type: 'object',
685
- properties: {
686
- work_item_id: {
687
- type: 'string',
688
- description: 'The ID of the work item',
689
- },
690
- title: {
691
- type: 'string',
692
- description: 'New title for the work item',
644
+ radar_item_id: {
645
+ type: ['string', 'null'],
646
+ description: 'Radar/initiative item ID to link to, or null to unlink',
693
647
  },
694
- project_slug: {
695
- type: 'string',
696
- description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
648
+ category_id: {
649
+ type: ['string', 'null'],
650
+ description: 'Category ID to set, or null to remove the category',
697
651
  },
698
- },
699
- required: ['work_item_id', 'title'],
700
- },
701
- },
702
- {
703
- name: 'update_work_item_initiative',
704
- description: 'Link or unlink a work item to/from a radar item (initiative). ' +
705
- 'Pass a radar_item_id to link, or null to remove the initiative link.',
706
- inputSchema: {
707
- type: 'object',
708
- properties: {
709
- work_item_id: {
710
- type: 'string',
711
- description: 'The ID of the work item',
652
+ priority: {
653
+ type: 'number',
654
+ description: 'Priority level: 0 (None), 1 (Low), 2 (Medium), 3 (High), 4 (Urgent)',
712
655
  },
713
- radar_item_id: {
656
+ size: {
714
657
  type: ['string', 'null'],
715
- description: 'Radar/initiative item ID to link to, or null to unlink',
658
+ enum: ['S', 'M', 'L', null],
659
+ description: 'Size estimate: "S" (small), "M" (medium), "L" (large), or null to remove',
716
660
  },
717
661
  project_slug: {
718
662
  type: 'string',
719
663
  description: 'Project slug (required for multi-project users, use list_projects to get slugs)',
720
664
  },
721
665
  },
722
- required: ['work_item_id', 'radar_item_id'],
666
+ required: ['work_item_id'],
723
667
  },
724
668
  },
725
669
  {
@@ -1302,6 +1246,7 @@ class WolfpackMCPServer {
1302
1246
  name: 'download_image',
1303
1247
  description: 'Download and view an image referenced in content fields. ' +
1304
1248
  'Content from work items, issues, wiki pages, journal entries, and comments may contain image references like `![alt](/api/files/images/...)`. ' +
1249
+ 'Issues may also have attachment images with URLs like `/api/teams/{team}/issues/{refId}/attachments/{id}`. ' +
1305
1250
  'Use this tool with that URL path to download and view the image. ' +
1306
1251
  'Requires mcp:images:read permission.',
1307
1252
  inputSchema: {
@@ -1309,7 +1254,7 @@ class WolfpackMCPServer {
1309
1254
  properties: {
1310
1255
  image_url: {
1311
1256
  type: 'string',
1312
- description: 'The image URL path found in content fields (e.g. "/api/files/images/{team}/{filename}").',
1257
+ description: 'The image URL path found in content fields (e.g. "/api/files/images/{team}/{filename}" or "/api/teams/{team}/issues/{refId}/attachments/{id}").',
1313
1258
  },
1314
1259
  },
1315
1260
  required: ['image_url'],
@@ -1450,78 +1395,61 @@ class WolfpackMCPServer {
1450
1395
  content: [{ type: 'text', text: 'Work item not found or not assigned to you' }],
1451
1396
  };
1452
1397
  }
1453
- case 'update_work_item_status': {
1454
- const parsed = UpdateWorkItemStatusSchema.parse(args);
1455
- const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
1456
- const workItem = await this.client.updateWorkItemStatus(parsed.work_item_id, parsed.status, teamSlug);
1457
- if (workItem) {
1458
- return {
1459
- content: [
1460
- {
1461
- type: 'text',
1462
- text: `Updated status of "${workItem.title}" to ${parsed.status}\n\n${JSON.stringify(stripUuids(workItem), null, 2)}`,
1463
- },
1464
- ],
1465
- };
1466
- }
1467
- return {
1468
- content: [{ type: 'text', text: 'Work item not found or not assigned to you' }],
1469
- };
1470
- }
1471
- case 'update_work_item_assignee': {
1472
- const parsed = UpdateWorkItemAssigneeSchema.parse(args);
1398
+ case 'update_work_item': {
1399
+ const parsed = UpdateWorkItemSchema.parse(args);
1473
1400
  const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
1474
- const workItem = await this.client.updateWorkItemAssignee(parsed.work_item_id, {
1475
- leadingUserId: parsed.leading_user_id,
1476
- }, teamSlug);
1401
+ // Build the fields object with only provided values
1402
+ const fields = {};
1403
+ if (parsed.title !== undefined)
1404
+ fields.title = parsed.title;
1405
+ if (parsed.status !== undefined)
1406
+ fields.status = parsed.status;
1407
+ if (parsed.leading_user_id !== undefined)
1408
+ fields.leadingUserId = parsed.leading_user_id;
1409
+ if (parsed.radar_item_id !== undefined)
1410
+ fields.radarItemId = parsed.radar_item_id;
1411
+ if (parsed.category_id !== undefined)
1412
+ fields.categoryId = parsed.category_id;
1413
+ if (parsed.priority !== undefined)
1414
+ fields.priority = parsed.priority;
1415
+ if (parsed.size !== undefined)
1416
+ fields.size = parsed.size;
1417
+ const workItem = await this.client.updateWorkItem(parsed.work_item_id, fields, teamSlug);
1477
1418
  if (workItem) {
1478
- const assigneeText = parsed.leading_user_id
1479
- ? `assigned to ${parsed.leading_user_id}`
1480
- : 'unassigned';
1481
- return {
1482
- content: [
1483
- {
1484
- type: 'text',
1485
- text: `Updated "${workItem.title}" - now ${assigneeText}\n\n${JSON.stringify(stripUuids(workItem), null, 2)}`,
1486
- },
1487
- ],
1488
- };
1489
- }
1490
- return {
1491
- content: [{ type: 'text', text: 'Work item not found' }],
1492
- };
1493
- }
1494
- case 'update_work_item_title': {
1495
- const parsed = UpdateWorkItemTitleSchema.parse(args);
1496
- const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
1497
- const workItem = await this.client.updateWorkItemTitle(parsed.work_item_id, parsed.title, teamSlug);
1498
- if (workItem) {
1499
- return {
1500
- content: [
1501
- {
1502
- type: 'text',
1503
- text: `Updated title to "${workItem.title}"\n\n${JSON.stringify(stripUuids(workItem), null, 2)}`,
1504
- },
1505
- ],
1506
- };
1507
- }
1508
- return {
1509
- content: [{ type: 'text', text: 'Work item not found' }],
1510
- };
1511
- }
1512
- case 'update_work_item_initiative': {
1513
- const parsed = UpdateWorkItemInitiativeSchema.parse(args);
1514
- const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
1515
- const workItem = await this.client.updateWorkItemInitiative(parsed.work_item_id, parsed.radar_item_id, teamSlug);
1516
- if (workItem) {
1517
- const initiativeText = parsed.radar_item_id
1518
- ? `linked to initiative ${parsed.radar_item_id}`
1519
- : 'unlinked from initiative';
1419
+ // Build a summary of what changed
1420
+ const changes = [];
1421
+ if (parsed.status !== undefined)
1422
+ changes.push(`status → ${parsed.status}`);
1423
+ if (parsed.title !== undefined)
1424
+ changes.push(`title → "${parsed.title}"`);
1425
+ if (parsed.leading_user_id !== undefined)
1426
+ changes.push(parsed.leading_user_id
1427
+ ? `assigned to ${workItem.leadingUser?.name || parsed.leading_user_id}`
1428
+ : 'unassigned');
1429
+ if (parsed.radar_item_id !== undefined)
1430
+ changes.push(parsed.radar_item_id ? `linked to initiative` : 'unlinked from initiative');
1431
+ if (parsed.category_id !== undefined)
1432
+ changes.push(parsed.category_id
1433
+ ? `category → ${workItem.category?.name || parsed.category_id}`
1434
+ : 'category removed');
1435
+ if (parsed.priority !== undefined) {
1436
+ const labels = {
1437
+ 0: 'None',
1438
+ 1: 'Low',
1439
+ 2: 'Medium',
1440
+ 3: 'High',
1441
+ 4: 'Urgent',
1442
+ };
1443
+ changes.push(`priority → ${labels[parsed.priority] || parsed.priority}`);
1444
+ }
1445
+ if (parsed.size !== undefined)
1446
+ changes.push(parsed.size ? `size → ${parsed.size}` : 'size removed');
1447
+ const summary = changes.length > 0 ? changes.join(', ') : 'no changes';
1520
1448
  return {
1521
1449
  content: [
1522
1450
  {
1523
1451
  type: 'text',
1524
- text: `Updated "${workItem.title}" - ${initiativeText}\n\n${JSON.stringify(stripUuids(workItem), null, 2)}`,
1452
+ text: `Updated "${workItem.title}" (${summary})\n\n${JSON.stringify(stripUuids(workItem), null, 2)}`,
1525
1453
  },
1526
1454
  ],
1527
1455
  };
@@ -1918,12 +1846,23 @@ class WolfpackMCPServer {
1918
1846
  }
1919
1847
  case 'download_image': {
1920
1848
  const parsed = DownloadImageSchema.parse(args);
1921
- const match = parsed.image_url.match(/\/api\/files\/images\/([^/]+)\/(.+)$/);
1922
- if (!match) {
1923
- throw new Error('Invalid image URL. Expected format: /api/files/images/{team}/{filename}');
1849
+ // Try S3-stored image pattern: /api/files/images/{team}/{filename}
1850
+ const s3Match = parsed.image_url.match(/\/api\/files\/images\/([^/]+)\/(.+)$/);
1851
+ // Try issue attachment pattern: /api/teams/{team}/issues/{refId}/attachments/{id}
1852
+ const attachmentMatch = parsed.image_url.match(/\/api\/teams\/([^/]+)\/issues\/(\d+)\/attachments\/([^/]+)$/);
1853
+ let base64;
1854
+ let mimeType;
1855
+ if (s3Match) {
1856
+ const [, team, filename] = s3Match;
1857
+ ({ base64, mimeType } = await this.client.downloadImage(team, filename));
1858
+ }
1859
+ else if (attachmentMatch) {
1860
+ const [, team, refId, attachmentId] = attachmentMatch;
1861
+ ({ base64, mimeType } = await this.client.downloadIssueAttachment(team, refId, attachmentId));
1862
+ }
1863
+ else {
1864
+ throw new Error('Invalid image URL. Expected format: /api/files/images/{team}/{filename} or /api/teams/{team}/issues/{refId}/attachments/{id}');
1924
1865
  }
1925
- const [, team, filename] = match;
1926
- const { base64, mimeType } = await this.client.downloadImage(team, filename);
1927
1866
  return {
1928
1867
  content: [
1929
1868
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolfpack-mcp",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "MCP server for Wolfpack AI-enhanced software delivery tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",