wolfpack-mcp 1.0.18 → 1.0.20

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/apiClient.js CHANGED
@@ -96,6 +96,34 @@ export class ApiClient {
96
96
  throw new Error(`Failed to PATCH ${path}: ${error}`);
97
97
  }
98
98
  }
99
+ async postMultipart(path, buffer, filename, mimeType) {
100
+ const url = `${config.apiUrl}${path}`;
101
+ const boundary = `----FormBoundary${Date.now()}`;
102
+ const header = `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename}"\r\nContent-Type: ${mimeType}\r\n\r\n`;
103
+ const footer = `\r\n--${boundary}--\r\n`;
104
+ const body = Buffer.concat([Buffer.from(header), buffer, Buffer.from(footer)]);
105
+ try {
106
+ const response = await fetch(url, {
107
+ method: 'POST',
108
+ headers: {
109
+ Authorization: `Bearer ${config.apiKey}`,
110
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
111
+ },
112
+ body,
113
+ });
114
+ if (!response.ok) {
115
+ const errorText = await response.text();
116
+ throw new ApiError(response.status, `API error: ${response.statusText} - ${errorText}`);
117
+ }
118
+ return (await response.json());
119
+ }
120
+ catch (error) {
121
+ if (error instanceof ApiError) {
122
+ throw error;
123
+ }
124
+ throw new Error(`Failed to POST multipart ${path}: ${error}`);
125
+ }
126
+ }
99
127
  async delete(path) {
100
128
  const url = `${config.apiUrl}${path}`;
101
129
  try {
package/dist/client.js CHANGED
@@ -6,6 +6,7 @@ const PAGE_SIZE = 50;
6
6
  export class WolfpackClient {
7
7
  api;
8
8
  teamId = null;
9
+ teamSlug = null;
9
10
  constructor() {
10
11
  this.api = new ApiClient();
11
12
  }
@@ -17,6 +18,7 @@ export class WolfpackClient {
17
18
  try {
18
19
  const team = await this.api.get(`/team/${teamSlug}`);
19
20
  this.teamId = team.id;
21
+ this.teamSlug = team.slug;
20
22
  return team.id;
21
23
  }
22
24
  catch (error) {
@@ -26,9 +28,15 @@ export class WolfpackClient {
26
28
  getTeamId() {
27
29
  return this.teamId;
28
30
  }
31
+ getTeamSlug() {
32
+ return this.teamSlug;
33
+ }
29
34
  setTeamId(teamId) {
30
35
  this.teamId = teamId;
31
36
  }
37
+ setTeamSlug(slug) {
38
+ this.teamSlug = slug;
39
+ }
32
40
  /**
33
41
  * List all teams the authenticated user is a member of.
34
42
  */
@@ -375,6 +383,13 @@ export class WolfpackClient {
375
383
  async createIssueComment(issueId, data) {
376
384
  return this.api.post(`/issues/${issueId}/comments`, data);
377
385
  }
386
+ /**
387
+ * Upload an image and get back a URL that can be used in markdown content.
388
+ */
389
+ async uploadImage(teamSlug, base64Data, filename, mimeType) {
390
+ const buffer = Buffer.from(base64Data, 'base64');
391
+ return this.api.postMultipart(`/teams/${teamSlug}/upload-image`, buffer, filename, mimeType);
392
+ }
378
393
  close() {
379
394
  // No cleanup needed for API client
380
395
  }
package/dist/index.js CHANGED
@@ -139,7 +139,10 @@ const GetIssueSchema = z.object({
139
139
  // Create schemas
140
140
  const CreateWorkItemSchema = z.object({
141
141
  title: z.string().describe('Title of the work item'),
142
- description: z.string().optional().describe('Description/notes for the work item (markdown)'),
142
+ description: z
143
+ .string()
144
+ .optional()
145
+ .describe('Description/notes for the work item (markdown). Supports base64-encoded images which will be auto-uploaded.'),
143
146
  status: z
144
147
  .string()
145
148
  .optional()
@@ -188,12 +191,17 @@ const GetWikiPageSchema = z.object({
188
191
  const CreateWikiPageSchema = z.object({
189
192
  slug: z.string().describe('URL slug for the page (without team prefix)'),
190
193
  title: z.string().describe('Page title'),
191
- content: z.string().describe('Page content (markdown)'),
194
+ content: z
195
+ .string()
196
+ .describe('Page content (markdown). Supports base64-encoded images (e.g., ![alt](data:image/png;base64,...)) which will be auto-uploaded.'),
192
197
  });
193
198
  const UpdateWikiPageSchema = z.object({
194
199
  page_id: z.string().describe('The page UUID'),
195
200
  title: z.string().optional().describe('Updated title'),
196
- content: z.string().optional().describe('Updated content (markdown)'),
201
+ content: z
202
+ .string()
203
+ .optional()
204
+ .describe('Updated content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
197
205
  });
198
206
  // Journal Entry schemas
199
207
  const ListJournalEntriesSchema = z.object({
@@ -206,22 +214,42 @@ const GetJournalEntrySchema = z.object({
206
214
  });
207
215
  const CreateJournalEntrySchema = z.object({
208
216
  title: z.string().describe('Entry title'),
209
- content: z.string().describe('Entry content (markdown)'),
217
+ content: z
218
+ .string()
219
+ .describe('Entry content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
210
220
  date: z.string().optional().describe('Entry date (ISO format, defaults to now)'),
211
221
  });
212
222
  const UpdateJournalEntrySchema = z.object({
213
223
  entry_id: z.string().describe('The entry UUID'),
214
224
  title: z.string().optional().describe('Updated title'),
215
- content: z.string().optional().describe('Updated content (markdown)'),
225
+ content: z
226
+ .string()
227
+ .optional()
228
+ .describe('Updated content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
216
229
  });
217
230
  // Comment schemas
218
231
  const CreateWorkItemCommentSchema = z.object({
219
232
  work_item_id: z.string().describe('The work item UUID'),
220
- content: z.string().describe('Comment content (markdown)'),
233
+ content: z
234
+ .string()
235
+ .describe('Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
221
236
  });
222
237
  const CreateIssueCommentSchema = z.object({
223
238
  issue_id: z.string().describe('The issue UUID'),
224
- content: z.string().describe('Comment content (markdown)'),
239
+ content: z
240
+ .string()
241
+ .describe('Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
242
+ });
243
+ // Image upload schema
244
+ const UploadImageSchema = z.object({
245
+ image_data: z
246
+ .string()
247
+ .describe('Base64-encoded image data (without the data:image/...;base64, prefix)'),
248
+ filename: z.string().describe('Filename for the image (e.g., "screenshot.png")'),
249
+ mime_type: z
250
+ .string()
251
+ .optional()
252
+ .describe('MIME type of the image (e.g., "image/png"). If omitted, inferred from filename extension.'),
225
253
  });
226
254
  // Helper to detect if a work item description contains a plan
227
255
  function hasPlan(description) {
@@ -531,7 +559,7 @@ class WolfpackMCPServer {
531
559
  title: { type: 'string', description: 'Title of the work item' },
532
560
  description: {
533
561
  type: 'string',
534
- description: 'Description/notes for the work item (markdown)',
562
+ description: 'Description/notes for the work item (markdown). Supports base64-encoded images which will be auto-uploaded.',
535
563
  },
536
564
  status: {
537
565
  type: 'string',
@@ -648,7 +676,10 @@ class WolfpackMCPServer {
648
676
  properties: {
649
677
  slug: { type: 'string', description: 'URL slug for the page (without team prefix)' },
650
678
  title: { type: 'string', description: 'Page title' },
651
- content: { type: 'string', description: 'Page content (markdown)' },
679
+ content: {
680
+ type: 'string',
681
+ description: 'Page content (markdown). Supports base64-encoded images (e.g., ![alt](data:image/png;base64,...)) which will be auto-uploaded.',
682
+ },
652
683
  },
653
684
  required: ['slug', 'title', 'content'],
654
685
  },
@@ -661,7 +692,10 @@ class WolfpackMCPServer {
661
692
  properties: {
662
693
  page_id: { type: 'string', description: 'The page UUID' },
663
694
  title: { type: 'string', description: 'Updated title' },
664
- content: { type: 'string', description: 'Updated content (markdown)' },
695
+ content: {
696
+ type: 'string',
697
+ description: 'Updated content (markdown). Supports base64-encoded images which will be auto-uploaded.',
698
+ },
665
699
  },
666
700
  required: ['page_id'],
667
701
  },
@@ -700,7 +734,10 @@ class WolfpackMCPServer {
700
734
  type: 'object',
701
735
  properties: {
702
736
  title: { type: 'string', description: 'Entry title' },
703
- content: { type: 'string', description: 'Entry content (markdown)' },
737
+ content: {
738
+ type: 'string',
739
+ description: 'Entry content (markdown). Supports base64-encoded images which will be auto-uploaded.',
740
+ },
704
741
  date: { type: 'string', description: 'Entry date (ISO format, defaults to now)' },
705
742
  },
706
743
  required: ['title', 'content'],
@@ -714,7 +751,10 @@ class WolfpackMCPServer {
714
751
  properties: {
715
752
  entry_id: { type: 'string', description: 'The entry UUID' },
716
753
  title: { type: 'string', description: 'Updated title' },
717
- content: { type: 'string', description: 'Updated content (markdown)' },
754
+ content: {
755
+ type: 'string',
756
+ description: 'Updated content (markdown). Supports base64-encoded images which will be auto-uploaded.',
757
+ },
718
758
  },
719
759
  required: ['entry_id'],
720
760
  },
@@ -731,7 +771,10 @@ class WolfpackMCPServer {
731
771
  type: 'object',
732
772
  properties: {
733
773
  work_item_id: { type: 'string', description: 'The work item UUID' },
734
- content: { type: 'string', description: 'Comment content (markdown)' },
774
+ content: {
775
+ type: 'string',
776
+ description: 'Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.',
777
+ },
735
778
  },
736
779
  required: ['work_item_id', 'content'],
737
780
  },
@@ -743,11 +786,39 @@ class WolfpackMCPServer {
743
786
  type: 'object',
744
787
  properties: {
745
788
  issue_id: { type: 'string', description: 'The issue UUID' },
746
- content: { type: 'string', description: 'Comment content (markdown)' },
789
+ content: {
790
+ type: 'string',
791
+ description: 'Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.',
792
+ },
747
793
  },
748
794
  required: ['issue_id', 'content'],
749
795
  },
750
796
  },
797
+ // Image tools
798
+ {
799
+ name: 'upload_image',
800
+ description: 'Upload an image and get back a permanent URL. Use this instead of embedding base64 images in content - ' +
801
+ 'it is much faster and more efficient. Returns a markdown image URL you can include in any content field. ' +
802
+ 'Requires mcp:images:upload permission.',
803
+ inputSchema: {
804
+ type: 'object',
805
+ properties: {
806
+ image_data: {
807
+ type: 'string',
808
+ description: 'Base64-encoded image data (without the data:image/...;base64, prefix). Just the raw base64 string.',
809
+ },
810
+ filename: {
811
+ type: 'string',
812
+ description: 'Filename for the image (e.g., "screenshot.png")',
813
+ },
814
+ mime_type: {
815
+ type: 'string',
816
+ description: 'MIME type of the image (e.g., "image/png"). If omitted, inferred from filename extension.',
817
+ },
818
+ },
819
+ required: ['image_data', 'filename'],
820
+ },
821
+ },
751
822
  ],
752
823
  }));
753
824
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -1163,6 +1234,23 @@ class WolfpackMCPServer {
1163
1234
  ],
1164
1235
  };
1165
1236
  }
1237
+ case 'upload_image': {
1238
+ const parsed = UploadImageSchema.parse(args);
1239
+ const teamSlug = this.client.getTeamSlug();
1240
+ if (!teamSlug) {
1241
+ throw new Error('No team selected. Use list_teams first.');
1242
+ }
1243
+ const mimeType = parsed.mime_type || this.inferMimeType(parsed.filename) || 'application/octet-stream';
1244
+ const result = await this.client.uploadImage(teamSlug, parsed.image_data, parsed.filename, mimeType);
1245
+ return {
1246
+ content: [
1247
+ {
1248
+ type: 'text',
1249
+ text: `Image uploaded successfully.\n\nURL: ${result.url}\n\nUse this URL in markdown: ![${parsed.filename}](${result.url})`,
1250
+ },
1251
+ ],
1252
+ };
1253
+ }
1166
1254
  default:
1167
1255
  throw new Error(`Unknown tool: ${name}`);
1168
1256
  }
@@ -1176,6 +1264,18 @@ class WolfpackMCPServer {
1176
1264
  }
1177
1265
  });
1178
1266
  }
1267
+ inferMimeType(filename) {
1268
+ const ext = filename.split('.').pop()?.toLowerCase();
1269
+ const mimeTypes = {
1270
+ png: 'image/png',
1271
+ jpg: 'image/jpeg',
1272
+ jpeg: 'image/jpeg',
1273
+ gif: 'image/gif',
1274
+ webp: 'image/webp',
1275
+ svg: 'image/svg+xml',
1276
+ };
1277
+ return ext ? mimeTypes[ext] || null : null;
1278
+ }
1179
1279
  async start() {
1180
1280
  // Start version check early (non-blocking)
1181
1281
  const updateCheckPromise = checkForUpdates();
@@ -1198,6 +1298,7 @@ class WolfpackMCPServer {
1198
1298
  if (teams.length === 1) {
1199
1299
  // Auto-select the only team
1200
1300
  this.client.setTeamId(teams[0].id);
1301
+ this.client.setTeamSlug(teams[0].slug);
1201
1302
  console.error(`Auto-selected team: ${teams[0].name} (${teams[0].slug})`);
1202
1303
  }
1203
1304
  else if (teams.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolfpack-mcp",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "MCP server for Wolfpack AI-enhanced software delivery tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",