wolfpack-mcp 1.0.24 → 1.0.26
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 +28 -0
- package/dist/client.js +17 -0
- package/dist/index.js +111 -4
- package/package.json +1 -1
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)
|
|
@@ -291,12 +304,15 @@ class WolfpackMCPServer {
|
|
|
291
304
|
// Work Item tools
|
|
292
305
|
{
|
|
293
306
|
name: 'list_work_items',
|
|
294
|
-
description: 'List work items (tasks/tickets). By default lists ALL items in the team, sorted by most recently updated. ' +
|
|
307
|
+
description: 'List work items (tasks/tickets) on the Kanban board or backlog. By default lists ALL items in the team, sorted by most recently updated. ' +
|
|
295
308
|
'Use assigned_to_id="me" to filter to only your assigned items. ' +
|
|
296
|
-
'TERMINOLOGY:
|
|
309
|
+
'TERMINOLOGY: "board" and "kanban" are synonymous - both refer to the Kanban board of work items. ' +
|
|
310
|
+
'The board has columns: "new" (to do), "doing" (in progress), "review" (pending review), "ready" (code done, awaiting deployment), "blocked", "completed" (deployed). ' +
|
|
297
311
|
'The "backlog" or "pending" status represents items not yet on the board. ' +
|
|
298
312
|
'By default, completed/closed items are excluded - use status="all" to include them. ' +
|
|
299
|
-
'
|
|
313
|
+
'IMPORTANT: Work items are NOT the same as issues. Work items live on the Kanban board/backlog. ' +
|
|
314
|
+
'If a user mentions "bugs" in the context of the board/kanban/backlog, they mean work items categorised as bug fixes - use this tool, not list_issues. ' +
|
|
315
|
+
'Use when user asks about "the board", "kanban", "work items", "tasks", "my work" (with assigned_to_id="me"), or "backlog".',
|
|
300
316
|
inputSchema: {
|
|
301
317
|
type: 'object',
|
|
302
318
|
properties: {
|
|
@@ -488,7 +504,11 @@ class WolfpackMCPServer {
|
|
|
488
504
|
// Issue tools
|
|
489
505
|
{
|
|
490
506
|
name: 'list_issues',
|
|
491
|
-
description: 'List issues
|
|
507
|
+
description: 'List issues from the Issue Tracker. Issues are a SEPARATE system from work items on the Kanban board. ' +
|
|
508
|
+
'Issues track bugs, feature requests, and other reports that may or may not have associated work items. ' +
|
|
509
|
+
'IMPORTANT: If the user asks about "bugs" or "tasks" in the context of the "board", "kanban", or "backlog", use list_work_items instead - those are work items, not issues. ' +
|
|
510
|
+
'Only use this tool when the user specifically asks about "issues", the "issue tracker", or is clearly referring to the issue tracking system. ' +
|
|
511
|
+
'Can filter by status, severity, and type.',
|
|
492
512
|
inputSchema: {
|
|
493
513
|
type: 'object',
|
|
494
514
|
properties: {
|
|
@@ -783,6 +803,29 @@ class WolfpackMCPServer {
|
|
|
783
803
|
required: ['issue_id', 'content'],
|
|
784
804
|
},
|
|
785
805
|
},
|
|
806
|
+
// Image tools
|
|
807
|
+
{
|
|
808
|
+
name: 'upload_image',
|
|
809
|
+
description: 'Upload an image file from disk and get back a permanent URL. ' +
|
|
810
|
+
'Reads the file directly from disk, avoiding slow base64 token generation. ' +
|
|
811
|
+
'Returns a markdown image URL you can include in any content field. ' +
|
|
812
|
+
'Requires mcp:images:upload permission.',
|
|
813
|
+
inputSchema: {
|
|
814
|
+
type: 'object',
|
|
815
|
+
properties: {
|
|
816
|
+
file_path: {
|
|
817
|
+
type: 'string',
|
|
818
|
+
description: 'Absolute path to an image file on disk (e.g., "/tmp/screenshot.png"). ' +
|
|
819
|
+
'Supported formats: JPEG, PNG, GIF, WebP. Max 5MB.',
|
|
820
|
+
},
|
|
821
|
+
team_id: {
|
|
822
|
+
type: 'number',
|
|
823
|
+
description: 'Team ID (required for multi-team users, use list_teams to get IDs)',
|
|
824
|
+
},
|
|
825
|
+
},
|
|
826
|
+
required: ['file_path'],
|
|
827
|
+
},
|
|
828
|
+
},
|
|
786
829
|
],
|
|
787
830
|
}));
|
|
788
831
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -1198,6 +1241,45 @@ class WolfpackMCPServer {
|
|
|
1198
1241
|
],
|
|
1199
1242
|
};
|
|
1200
1243
|
}
|
|
1244
|
+
case 'upload_image': {
|
|
1245
|
+
const parsed = UploadImageSchema.parse(args);
|
|
1246
|
+
const teamSlug = await this.client.resolveSlug(parsed.team_id || this.client.getTeamId() || undefined);
|
|
1247
|
+
const filePath = resolve(parsed.file_path);
|
|
1248
|
+
// Validate file extension
|
|
1249
|
+
const ext = extname(filePath).toLowerCase();
|
|
1250
|
+
const allowedExtensions = {
|
|
1251
|
+
'.png': 'image/png',
|
|
1252
|
+
'.jpg': 'image/jpeg',
|
|
1253
|
+
'.jpeg': 'image/jpeg',
|
|
1254
|
+
'.gif': 'image/gif',
|
|
1255
|
+
'.webp': 'image/webp',
|
|
1256
|
+
};
|
|
1257
|
+
const mimeType = allowedExtensions[ext];
|
|
1258
|
+
if (!mimeType) {
|
|
1259
|
+
throw new Error(`Unsupported image format "${ext}". Supported: ${Object.keys(allowedExtensions).join(', ')}`);
|
|
1260
|
+
}
|
|
1261
|
+
// Check file exists and size
|
|
1262
|
+
const fileStat = await stat(filePath);
|
|
1263
|
+
const maxSize = 5 * 1024 * 1024; // 5MB
|
|
1264
|
+
if (fileStat.size > maxSize) {
|
|
1265
|
+
throw new Error(`File too large (${fileStat.size} bytes). Maximum size is 5MB.`);
|
|
1266
|
+
}
|
|
1267
|
+
// Validate magic bytes to confirm it's actually an image
|
|
1268
|
+
const buffer = await readFile(filePath);
|
|
1269
|
+
if (!this.isImageBuffer(buffer)) {
|
|
1270
|
+
throw new Error('File does not appear to be a valid image.');
|
|
1271
|
+
}
|
|
1272
|
+
const filename = basename(filePath);
|
|
1273
|
+
const result = await this.client.uploadImage(teamSlug, buffer.toString('base64'), filename, mimeType);
|
|
1274
|
+
return {
|
|
1275
|
+
content: [
|
|
1276
|
+
{
|
|
1277
|
+
type: 'text',
|
|
1278
|
+
text: `Image uploaded successfully.\n\nURL: ${result.url}\n\nUse this URL in markdown: `,
|
|
1279
|
+
},
|
|
1280
|
+
],
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1201
1283
|
default:
|
|
1202
1284
|
throw new Error(`Unknown tool: ${name}`);
|
|
1203
1285
|
}
|
|
@@ -1211,6 +1293,31 @@ class WolfpackMCPServer {
|
|
|
1211
1293
|
}
|
|
1212
1294
|
});
|
|
1213
1295
|
}
|
|
1296
|
+
isImageBuffer(buffer) {
|
|
1297
|
+
if (buffer.length < 4)
|
|
1298
|
+
return false;
|
|
1299
|
+
// PNG: 89 50 4E 47
|
|
1300
|
+
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47)
|
|
1301
|
+
return true;
|
|
1302
|
+
// JPEG: FF D8 FF
|
|
1303
|
+
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff)
|
|
1304
|
+
return true;
|
|
1305
|
+
// GIF: 47 49 46 38
|
|
1306
|
+
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38)
|
|
1307
|
+
return true;
|
|
1308
|
+
// WebP: 52 49 46 46 ... 57 45 42 50
|
|
1309
|
+
if (buffer.length >= 12 &&
|
|
1310
|
+
buffer[0] === 0x52 &&
|
|
1311
|
+
buffer[1] === 0x49 &&
|
|
1312
|
+
buffer[2] === 0x46 &&
|
|
1313
|
+
buffer[3] === 0x46 &&
|
|
1314
|
+
buffer[8] === 0x57 &&
|
|
1315
|
+
buffer[9] === 0x45 &&
|
|
1316
|
+
buffer[10] === 0x42 &&
|
|
1317
|
+
buffer[11] === 0x50)
|
|
1318
|
+
return true;
|
|
1319
|
+
return false;
|
|
1320
|
+
}
|
|
1214
1321
|
async start() {
|
|
1215
1322
|
// Start version check early (non-blocking)
|
|
1216
1323
|
const updateCheckPromise = checkForUpdates();
|