wolfpack-mcp 1.0.19 → 1.0.20
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 +15 -0
- package/dist/index.js +66 -0
- 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
|
@@ -6,6 +6,7 @@ const PAGE_SIZE = 50;
|
|
|
6
6
|
export class WolfpackClient {
|
|
7
7
|
api;
|
|
8
8
|
teamId = null;
|
|
9
|
+
teamSlug = null;
|
|
9
10
|
constructor() {
|
|
10
11
|
this.api = new ApiClient();
|
|
11
12
|
}
|
|
@@ -17,6 +18,7 @@ export class WolfpackClient {
|
|
|
17
18
|
try {
|
|
18
19
|
const team = await this.api.get(`/team/${teamSlug}`);
|
|
19
20
|
this.teamId = team.id;
|
|
21
|
+
this.teamSlug = team.slug;
|
|
20
22
|
return team.id;
|
|
21
23
|
}
|
|
22
24
|
catch (error) {
|
|
@@ -26,9 +28,15 @@ export class WolfpackClient {
|
|
|
26
28
|
getTeamId() {
|
|
27
29
|
return this.teamId;
|
|
28
30
|
}
|
|
31
|
+
getTeamSlug() {
|
|
32
|
+
return this.teamSlug;
|
|
33
|
+
}
|
|
29
34
|
setTeamId(teamId) {
|
|
30
35
|
this.teamId = teamId;
|
|
31
36
|
}
|
|
37
|
+
setTeamSlug(slug) {
|
|
38
|
+
this.teamSlug = slug;
|
|
39
|
+
}
|
|
32
40
|
/**
|
|
33
41
|
* List all teams the authenticated user is a member of.
|
|
34
42
|
*/
|
|
@@ -375,6 +383,13 @@ export class WolfpackClient {
|
|
|
375
383
|
async createIssueComment(issueId, data) {
|
|
376
384
|
return this.api.post(`/issues/${issueId}/comments`, data);
|
|
377
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Upload an image and get back a URL that can be used in markdown content.
|
|
388
|
+
*/
|
|
389
|
+
async uploadImage(teamSlug, base64Data, filename, mimeType) {
|
|
390
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
391
|
+
return this.api.postMultipart(`/teams/${teamSlug}/upload-image`, buffer, filename, mimeType);
|
|
392
|
+
}
|
|
378
393
|
close() {
|
|
379
394
|
// No cleanup needed for API client
|
|
380
395
|
}
|
package/dist/index.js
CHANGED
|
@@ -240,6 +240,17 @@ const CreateIssueCommentSchema = z.object({
|
|
|
240
240
|
.string()
|
|
241
241
|
.describe('Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
|
|
242
242
|
});
|
|
243
|
+
// Image upload schema
|
|
244
|
+
const UploadImageSchema = z.object({
|
|
245
|
+
image_data: z
|
|
246
|
+
.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.'),
|
|
253
|
+
});
|
|
243
254
|
// Helper to detect if a work item description contains a plan
|
|
244
255
|
function hasPlan(description) {
|
|
245
256
|
if (!description)
|
|
@@ -783,6 +794,31 @@ class WolfpackMCPServer {
|
|
|
783
794
|
required: ['issue_id', 'content'],
|
|
784
795
|
},
|
|
785
796
|
},
|
|
797
|
+
// Image tools
|
|
798
|
+
{
|
|
799
|
+
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. ' +
|
|
802
|
+
'Requires mcp:images:upload permission.',
|
|
803
|
+
inputSchema: {
|
|
804
|
+
type: 'object',
|
|
805
|
+
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: {
|
|
815
|
+
type: 'string',
|
|
816
|
+
description: 'MIME type of the image (e.g., "image/png"). If omitted, inferred from filename extension.',
|
|
817
|
+
},
|
|
818
|
+
},
|
|
819
|
+
required: ['image_data', 'filename'],
|
|
820
|
+
},
|
|
821
|
+
},
|
|
786
822
|
],
|
|
787
823
|
}));
|
|
788
824
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -1198,6 +1234,23 @@ class WolfpackMCPServer {
|
|
|
1198
1234
|
],
|
|
1199
1235
|
};
|
|
1200
1236
|
}
|
|
1237
|
+
case 'upload_image': {
|
|
1238
|
+
const parsed = UploadImageSchema.parse(args);
|
|
1239
|
+
const teamSlug = this.client.getTeamSlug();
|
|
1240
|
+
if (!teamSlug) {
|
|
1241
|
+
throw new Error('No team selected. Use list_teams first.');
|
|
1242
|
+
}
|
|
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);
|
|
1245
|
+
return {
|
|
1246
|
+
content: [
|
|
1247
|
+
{
|
|
1248
|
+
type: 'text',
|
|
1249
|
+
text: `Image uploaded successfully.\n\nURL: ${result.url}\n\nUse this URL in markdown: `,
|
|
1250
|
+
},
|
|
1251
|
+
],
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1201
1254
|
default:
|
|
1202
1255
|
throw new Error(`Unknown tool: ${name}`);
|
|
1203
1256
|
}
|
|
@@ -1211,6 +1264,18 @@ class WolfpackMCPServer {
|
|
|
1211
1264
|
}
|
|
1212
1265
|
});
|
|
1213
1266
|
}
|
|
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;
|
|
1278
|
+
}
|
|
1214
1279
|
async start() {
|
|
1215
1280
|
// Start version check early (non-blocking)
|
|
1216
1281
|
const updateCheckPromise = checkForUpdates();
|
|
@@ -1233,6 +1298,7 @@ class WolfpackMCPServer {
|
|
|
1233
1298
|
if (teams.length === 1) {
|
|
1234
1299
|
// Auto-select the only team
|
|
1235
1300
|
this.client.setTeamId(teams[0].id);
|
|
1301
|
+
this.client.setTeamSlug(teams[0].slug);
|
|
1236
1302
|
console.error(`Auto-selected team: ${teams[0].name} (${teams[0].slug})`);
|
|
1237
1303
|
}
|
|
1238
1304
|
else if (teams.length === 0) {
|