wolfpack-mcp 1.0.22 → 1.0.24
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 +0 -28
- package/dist/client.js +2 -39
- package/dist/index.js +0 -101
- package/package.json +1 -1
package/dist/apiClient.js
CHANGED
|
@@ -96,34 +96,6 @@ 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
|
-
}
|
|
127
99
|
async delete(path) {
|
|
128
100
|
const url = `${config.apiUrl}${path}`;
|
|
129
101
|
try {
|
package/dist/client.js
CHANGED
|
@@ -6,7 +6,6 @@ const PAGE_SIZE = 50;
|
|
|
6
6
|
export class WolfpackClient {
|
|
7
7
|
api;
|
|
8
8
|
teamId = null;
|
|
9
|
-
teamSlug = null;
|
|
10
9
|
constructor() {
|
|
11
10
|
this.api = new ApiClient();
|
|
12
11
|
}
|
|
@@ -18,7 +17,6 @@ export class WolfpackClient {
|
|
|
18
17
|
try {
|
|
19
18
|
const team = await this.api.get(`/team/${teamSlug}`);
|
|
20
19
|
this.teamId = team.id;
|
|
21
|
-
this.teamSlug = team.slug;
|
|
22
20
|
return team.id;
|
|
23
21
|
}
|
|
24
22
|
catch (error) {
|
|
@@ -28,46 +26,15 @@ export class WolfpackClient {
|
|
|
28
26
|
getTeamId() {
|
|
29
27
|
return this.teamId;
|
|
30
28
|
}
|
|
31
|
-
getTeamSlug() {
|
|
32
|
-
return this.teamSlug;
|
|
33
|
-
}
|
|
34
29
|
setTeamId(teamId) {
|
|
35
30
|
this.teamId = teamId;
|
|
36
31
|
}
|
|
37
|
-
setTeamSlug(slug) {
|
|
38
|
-
this.teamSlug = slug;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Resolve a team slug from a team ID, the cached slug, or by fetching teams.
|
|
42
|
-
*/
|
|
43
|
-
async resolveSlug(teamId) {
|
|
44
|
-
// If a specific team ID is provided, fetch teams to find its slug
|
|
45
|
-
if (teamId) {
|
|
46
|
-
const teams = await this.listTeams();
|
|
47
|
-
const team = teams.find((t) => t.id === teamId);
|
|
48
|
-
if (!team)
|
|
49
|
-
throw new Error(`Team with ID ${teamId} not found`);
|
|
50
|
-
return team.slug;
|
|
51
|
-
}
|
|
52
|
-
// Fall back to cached slug
|
|
53
|
-
if (this.teamSlug)
|
|
54
|
-
return this.teamSlug;
|
|
55
|
-
// Try to resolve from cached team ID
|
|
56
|
-
if (this.teamId) {
|
|
57
|
-
const teams = await this.listTeams();
|
|
58
|
-
const team = teams.find((t) => t.id === this.teamId);
|
|
59
|
-
if (!team)
|
|
60
|
-
throw new Error(`Team with ID ${this.teamId} not found`);
|
|
61
|
-
this.teamSlug = team.slug;
|
|
62
|
-
return team.slug;
|
|
63
|
-
}
|
|
64
|
-
throw new Error('No team selected. Use list_teams first.');
|
|
65
|
-
}
|
|
66
32
|
/**
|
|
67
33
|
* List all teams the authenticated user is a member of.
|
|
68
34
|
*/
|
|
69
35
|
async listTeams() {
|
|
70
|
-
|
|
36
|
+
const response = await this.api.get('/teams');
|
|
37
|
+
return response.teams;
|
|
71
38
|
}
|
|
72
39
|
// Helper to fetch all pages of a paginated endpoint
|
|
73
40
|
async fetchAllPages(endpoint, baseParams) {
|
|
@@ -412,10 +379,6 @@ export class WolfpackClient {
|
|
|
412
379
|
/**
|
|
413
380
|
* Upload an image and get back a URL that can be used in markdown content.
|
|
414
381
|
*/
|
|
415
|
-
async uploadImage(teamSlug, base64Data, filename, mimeType) {
|
|
416
|
-
const buffer = Buffer.from(base64Data, 'base64');
|
|
417
|
-
return this.api.postMultipart(`/teams/${teamSlug}/upload-image`, buffer, filename, mimeType);
|
|
418
|
-
}
|
|
419
382
|
close() {
|
|
420
383
|
// No cleanup needed for API client
|
|
421
384
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,6 @@ 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';
|
|
9
7
|
import { WolfpackClient } from './client.js';
|
|
10
8
|
import { validateConfig, config } from './config.js';
|
|
11
9
|
// Get current package version
|
|
@@ -242,17 +240,6 @@ const CreateIssueCommentSchema = z.object({
|
|
|
242
240
|
.string()
|
|
243
241
|
.describe('Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
|
|
244
242
|
});
|
|
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
|
-
});
|
|
256
243
|
// Helper to detect if a work item description contains a plan
|
|
257
244
|
function hasPlan(description) {
|
|
258
245
|
if (!description)
|
|
@@ -796,29 +783,6 @@ class WolfpackMCPServer {
|
|
|
796
783
|
required: ['issue_id', 'content'],
|
|
797
784
|
},
|
|
798
785
|
},
|
|
799
|
-
// Image tools
|
|
800
|
-
{
|
|
801
|
-
name: 'upload_image',
|
|
802
|
-
description: 'Upload an image file from disk and get back a permanent URL. Much faster than embedding base64 in content. ' +
|
|
803
|
-
'Pass the absolute file path - the file is read directly from disk without going through the LLM. ' +
|
|
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
|
-
},
|
|
822
786
|
],
|
|
823
787
|
}));
|
|
824
788
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -1234,45 +1198,6 @@ class WolfpackMCPServer {
|
|
|
1234
1198
|
],
|
|
1235
1199
|
};
|
|
1236
1200
|
}
|
|
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: `,
|
|
1272
|
-
},
|
|
1273
|
-
],
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1276
1201
|
default:
|
|
1277
1202
|
throw new Error(`Unknown tool: ${name}`);
|
|
1278
1203
|
}
|
|
@@ -1286,31 +1211,6 @@ class WolfpackMCPServer {
|
|
|
1286
1211
|
}
|
|
1287
1212
|
});
|
|
1288
1213
|
}
|
|
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
|
-
}
|
|
1314
1214
|
async start() {
|
|
1315
1215
|
// Start version check early (non-blocking)
|
|
1316
1216
|
const updateCheckPromise = checkForUpdates();
|
|
@@ -1333,7 +1233,6 @@ class WolfpackMCPServer {
|
|
|
1333
1233
|
if (teams.length === 1) {
|
|
1334
1234
|
// Auto-select the only team
|
|
1335
1235
|
this.client.setTeamId(teams[0].id);
|
|
1336
|
-
this.client.setTeamSlug(teams[0].slug);
|
|
1337
1236
|
console.error(`Auto-selected team: ${teams[0].name} (${teams[0].slug})`);
|
|
1338
1237
|
}
|
|
1339
1238
|
else if (teams.length === 0) {
|