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.
- package/dist/index.js +64 -34
- 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
|
-
|
|
247
|
+
file_path: z
|
|
246
248
|
.string()
|
|
247
|
-
.describe('
|
|
248
|
-
|
|
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.
|
|
801
|
-
'
|
|
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
|
-
|
|
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: '
|
|
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: ['
|
|
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
|
|
1244
|
-
|
|
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: `,
|
|
1250
1267
|
},
|
|
1251
1268
|
],
|
|
1252
1269
|
};
|
|
@@ -1264,17 +1281,30 @@ class WolfpackMCPServer {
|
|
|
1264
1281
|
}
|
|
1265
1282
|
});
|
|
1266
1283
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
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)
|