wolfpack-mcp 1.0.19 → 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
@@ -240,6 +240,17 @@ const CreateIssueCommentSchema = z.object({
240
240
  .string()
241
241
  .describe('Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
242
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.'),
253
+ });
243
254
  // Helper to detect if a work item description contains a plan
244
255
  function hasPlan(description) {
245
256
  if (!description)
@@ -783,6 +794,31 @@ class WolfpackMCPServer {
783
794
  required: ['issue_id', 'content'],
784
795
  },
785
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
+ },
786
822
  ],
787
823
  }));
788
824
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -1198,6 +1234,23 @@ class WolfpackMCPServer {
1198
1234
  ],
1199
1235
  };
1200
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
+ }
1201
1254
  default:
1202
1255
  throw new Error(`Unknown tool: ${name}`);
1203
1256
  }
@@ -1211,6 +1264,18 @@ class WolfpackMCPServer {
1211
1264
  }
1212
1265
  });
1213
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
+ }
1214
1279
  async start() {
1215
1280
  // Start version check early (non-blocking)
1216
1281
  const updateCheckPromise = checkForUpdates();
@@ -1233,6 +1298,7 @@ class WolfpackMCPServer {
1233
1298
  if (teams.length === 1) {
1234
1299
  // Auto-select the only team
1235
1300
  this.client.setTeamId(teams[0].id);
1301
+ this.client.setTeamSlug(teams[0].slug);
1236
1302
  console.error(`Auto-selected team: ${teams[0].name} (${teams[0].slug})`);
1237
1303
  }
1238
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.19",
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",