wolfpack-mcp 1.0.24 → 1.0.25

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
@@ -29,6 +29,19 @@ export class WolfpackClient {
29
29
  setTeamId(teamId) {
30
30
  this.teamId = teamId;
31
31
  }
32
+ /**
33
+ * Resolve a team slug from a team ID or the cached team ID.
34
+ */
35
+ async resolveSlug(teamId) {
36
+ const id = teamId || this.teamId;
37
+ if (!id)
38
+ throw new Error('No team selected. Use list_teams first.');
39
+ const teams = await this.listTeams();
40
+ const team = teams.find((t) => t.id === id);
41
+ if (!team)
42
+ throw new Error(`Team with ID ${id} not found`);
43
+ return team.slug;
44
+ }
32
45
  /**
33
46
  * List all teams the authenticated user is a member of.
34
47
  */
@@ -379,6 +392,10 @@ export class WolfpackClient {
379
392
  /**
380
393
  * Upload an image and get back a URL that can be used in markdown content.
381
394
  */
395
+ async uploadImage(teamSlug, base64Data, filename, mimeType) {
396
+ const buffer = Buffer.from(base64Data, 'base64');
397
+ return this.api.postMultipart(`/teams/${teamSlug}/upload-image`, buffer, filename, mimeType);
398
+ }
382
399
  close() {
383
400
  // No cleanup needed for API client
384
401
  }
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { z } from 'zod';
6
6
  import { createRequire } from 'module';
7
+ import { readFile, stat } from 'fs/promises';
8
+ import { basename, extname, resolve } from 'path';
7
9
  import { WolfpackClient } from './client.js';
8
10
  import { validateConfig, config } from './config.js';
9
11
  // Get current package version
@@ -240,6 +242,17 @@ const CreateIssueCommentSchema = z.object({
240
242
  .string()
241
243
  .describe('Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
242
244
  });
245
+ // Image upload schema
246
+ const UploadImageSchema = z.object({
247
+ file_path: z
248
+ .string()
249
+ .describe('Absolute path to an image file on disk (e.g., "/tmp/screenshot.png"). ' +
250
+ 'Supported formats: JPEG, PNG, GIF, WebP. Max 5MB.'),
251
+ team_id: z
252
+ .number()
253
+ .optional()
254
+ .describe('Team ID (required for multi-team users, use list_teams to get IDs)'),
255
+ });
243
256
  // Helper to detect if a work item description contains a plan
244
257
  function hasPlan(description) {
245
258
  if (!description)
@@ -783,6 +796,29 @@ class WolfpackMCPServer {
783
796
  required: ['issue_id', 'content'],
784
797
  },
785
798
  },
799
+ // Image tools
800
+ {
801
+ name: 'upload_image',
802
+ description: 'Upload an image file from disk and get back a permanent URL. ' +
803
+ 'Reads the file directly from disk, avoiding slow base64 token generation. ' +
804
+ 'Returns a markdown image URL you can include in any content field. ' +
805
+ 'Requires mcp:images:upload permission.',
806
+ inputSchema: {
807
+ type: 'object',
808
+ properties: {
809
+ file_path: {
810
+ type: 'string',
811
+ description: 'Absolute path to an image file on disk (e.g., "/tmp/screenshot.png"). ' +
812
+ 'Supported formats: JPEG, PNG, GIF, WebP. Max 5MB.',
813
+ },
814
+ team_id: {
815
+ type: 'number',
816
+ description: 'Team ID (required for multi-team users, use list_teams to get IDs)',
817
+ },
818
+ },
819
+ required: ['file_path'],
820
+ },
821
+ },
786
822
  ],
787
823
  }));
788
824
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -1198,6 +1234,45 @@ class WolfpackMCPServer {
1198
1234
  ],
1199
1235
  };
1200
1236
  }
1237
+ case 'upload_image': {
1238
+ const parsed = UploadImageSchema.parse(args);
1239
+ const teamSlug = await this.client.resolveSlug(parsed.team_id || this.client.getTeamId() || undefined);
1240
+ const filePath = resolve(parsed.file_path);
1241
+ // Validate file extension
1242
+ const ext = extname(filePath).toLowerCase();
1243
+ const allowedExtensions = {
1244
+ '.png': 'image/png',
1245
+ '.jpg': 'image/jpeg',
1246
+ '.jpeg': 'image/jpeg',
1247
+ '.gif': 'image/gif',
1248
+ '.webp': 'image/webp',
1249
+ };
1250
+ const mimeType = allowedExtensions[ext];
1251
+ if (!mimeType) {
1252
+ throw new Error(`Unsupported image format "${ext}". Supported: ${Object.keys(allowedExtensions).join(', ')}`);
1253
+ }
1254
+ // Check file exists and size
1255
+ const fileStat = await stat(filePath);
1256
+ const maxSize = 5 * 1024 * 1024; // 5MB
1257
+ if (fileStat.size > maxSize) {
1258
+ throw new Error(`File too large (${fileStat.size} bytes). Maximum size is 5MB.`);
1259
+ }
1260
+ // Validate magic bytes to confirm it's actually an image
1261
+ const buffer = await readFile(filePath);
1262
+ if (!this.isImageBuffer(buffer)) {
1263
+ throw new Error('File does not appear to be a valid image.');
1264
+ }
1265
+ const filename = basename(filePath);
1266
+ const result = await this.client.uploadImage(teamSlug, buffer.toString('base64'), filename, mimeType);
1267
+ return {
1268
+ content: [
1269
+ {
1270
+ type: 'text',
1271
+ text: `Image uploaded successfully.\n\nURL: ${result.url}\n\nUse this URL in markdown: ![${filename}](${result.url})`,
1272
+ },
1273
+ ],
1274
+ };
1275
+ }
1201
1276
  default:
1202
1277
  throw new Error(`Unknown tool: ${name}`);
1203
1278
  }
@@ -1211,6 +1286,31 @@ class WolfpackMCPServer {
1211
1286
  }
1212
1287
  });
1213
1288
  }
1289
+ isImageBuffer(buffer) {
1290
+ if (buffer.length < 4)
1291
+ return false;
1292
+ // PNG: 89 50 4E 47
1293
+ if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47)
1294
+ return true;
1295
+ // JPEG: FF D8 FF
1296
+ if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff)
1297
+ return true;
1298
+ // GIF: 47 49 46 38
1299
+ if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38)
1300
+ return true;
1301
+ // WebP: 52 49 46 46 ... 57 45 42 50
1302
+ if (buffer.length >= 12 &&
1303
+ buffer[0] === 0x52 &&
1304
+ buffer[1] === 0x49 &&
1305
+ buffer[2] === 0x46 &&
1306
+ buffer[3] === 0x46 &&
1307
+ buffer[8] === 0x57 &&
1308
+ buffer[9] === 0x45 &&
1309
+ buffer[10] === 0x42 &&
1310
+ buffer[11] === 0x50)
1311
+ return true;
1312
+ return false;
1313
+ }
1214
1314
  async start() {
1215
1315
  // Start version check early (non-blocking)
1216
1316
  const updateCheckPromise = checkForUpdates();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolfpack-mcp",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
4
4
  "description": "MCP server for Wolfpack AI-enhanced software delivery tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",