wolfpack-mcp 1.0.20 → 1.0.21

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 (2) hide show
  1. package/dist/index.js +64 -34
  2. package/package.json +1 -1
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 { resolve, basename, extname } from 'path';
7
9
  import { WolfpackClient } from './client.js';
8
10
  import { validateConfig, config } from './config.js';
9
11
  // Get current package version
@@ -242,14 +244,10 @@ const CreateIssueCommentSchema = z.object({
242
244
  });
243
245
  // Image upload schema
244
246
  const UploadImageSchema = z.object({
245
- image_data: z
247
+ file_path: z
246
248
  .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.'),
249
+ .describe('Absolute path to an image file on disk (e.g., "/tmp/screenshot.png"). ' +
250
+ 'Supported formats: JPEG, PNG, GIF, WebP. Max 5MB.'),
253
251
  });
254
252
  // Helper to detect if a work item description contains a plan
255
253
  function hasPlan(description) {
@@ -797,26 +795,20 @@ class WolfpackMCPServer {
797
795
  // Image tools
798
796
  {
799
797
  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. ' +
798
+ description: 'Upload an image file from disk and get back a permanent URL. Much faster than embedding base64 in content. ' +
799
+ 'Pass the absolute file path - the file is read directly from disk without going through the LLM. ' +
800
+ 'Returns a markdown image URL you can include in any content field. ' +
802
801
  'Requires mcp:images:upload permission.',
803
802
  inputSchema: {
804
803
  type: 'object',
805
804
  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: {
805
+ file_path: {
815
806
  type: 'string',
816
- description: 'MIME type of the image (e.g., "image/png"). If omitted, inferred from filename extension.',
807
+ description: 'Absolute path to an image file on disk (e.g., "/tmp/screenshot.png"). ' +
808
+ 'Supported formats: JPEG, PNG, GIF, WebP. Max 5MB.',
817
809
  },
818
810
  },
819
- required: ['image_data', 'filename'],
811
+ required: ['file_path'],
820
812
  },
821
813
  },
822
814
  ],
@@ -1240,13 +1232,38 @@ class WolfpackMCPServer {
1240
1232
  if (!teamSlug) {
1241
1233
  throw new Error('No team selected. Use list_teams first.');
1242
1234
  }
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);
1235
+ const filePath = resolve(parsed.file_path);
1236
+ // Validate file extension
1237
+ const ext = extname(filePath).toLowerCase();
1238
+ const allowedExtensions = {
1239
+ '.png': 'image/png',
1240
+ '.jpg': 'image/jpeg',
1241
+ '.jpeg': 'image/jpeg',
1242
+ '.gif': 'image/gif',
1243
+ '.webp': 'image/webp',
1244
+ };
1245
+ const mimeType = allowedExtensions[ext];
1246
+ if (!mimeType) {
1247
+ throw new Error(`Unsupported image format "${ext}". Supported: ${Object.keys(allowedExtensions).join(', ')}`);
1248
+ }
1249
+ // Check file exists and size
1250
+ const fileStat = await stat(filePath);
1251
+ const maxSize = 5 * 1024 * 1024; // 5MB
1252
+ if (fileStat.size > maxSize) {
1253
+ throw new Error(`File too large (${fileStat.size} bytes). Maximum size is 5MB.`);
1254
+ }
1255
+ // Validate magic bytes to confirm it's actually an image
1256
+ const buffer = await readFile(filePath);
1257
+ if (!this.isImageBuffer(buffer)) {
1258
+ throw new Error('File does not appear to be a valid image.');
1259
+ }
1260
+ const filename = basename(filePath);
1261
+ const result = await this.client.uploadImage(teamSlug, buffer.toString('base64'), filename, mimeType);
1245
1262
  return {
1246
1263
  content: [
1247
1264
  {
1248
1265
  type: 'text',
1249
- text: `Image uploaded successfully.\n\nURL: ${result.url}\n\nUse this URL in markdown: ![${parsed.filename}](${result.url})`,
1266
+ text: `Image uploaded successfully.\n\nURL: ${result.url}\n\nUse this URL in markdown: ![${filename}](${result.url})`,
1250
1267
  },
1251
1268
  ],
1252
1269
  };
@@ -1264,17 +1281,30 @@ class WolfpackMCPServer {
1264
1281
  }
1265
1282
  });
1266
1283
  }
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;
1284
+ isImageBuffer(buffer) {
1285
+ if (buffer.length < 4)
1286
+ return false;
1287
+ // PNG: 89 50 4E 47
1288
+ if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47)
1289
+ return true;
1290
+ // JPEG: FF D8 FF
1291
+ if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff)
1292
+ return true;
1293
+ // GIF: 47 49 46 38
1294
+ if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38)
1295
+ return true;
1296
+ // WebP: 52 49 46 46 ... 57 45 42 50
1297
+ if (buffer.length >= 12 &&
1298
+ buffer[0] === 0x52 &&
1299
+ buffer[1] === 0x49 &&
1300
+ buffer[2] === 0x46 &&
1301
+ buffer[3] === 0x46 &&
1302
+ buffer[8] === 0x57 &&
1303
+ buffer[9] === 0x45 &&
1304
+ buffer[10] === 0x42 &&
1305
+ buffer[11] === 0x50)
1306
+ return true;
1307
+ return false;
1278
1308
  }
1279
1309
  async start() {
1280
1310
  // Start version check early (non-blocking)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolfpack-mcp",
3
- "version": "1.0.20",
3
+ "version": "1.0.21",
4
4
  "description": "MCP server for Wolfpack AI-enhanced software delivery tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",