wolfpack-mcp 1.0.18 → 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 +115 -14
- 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
|
@@ -139,7 +139,10 @@ const GetIssueSchema = z.object({
|
|
|
139
139
|
// Create schemas
|
|
140
140
|
const CreateWorkItemSchema = z.object({
|
|
141
141
|
title: z.string().describe('Title of the work item'),
|
|
142
|
-
description: z
|
|
142
|
+
description: z
|
|
143
|
+
.string()
|
|
144
|
+
.optional()
|
|
145
|
+
.describe('Description/notes for the work item (markdown). Supports base64-encoded images which will be auto-uploaded.'),
|
|
143
146
|
status: z
|
|
144
147
|
.string()
|
|
145
148
|
.optional()
|
|
@@ -188,12 +191,17 @@ const GetWikiPageSchema = z.object({
|
|
|
188
191
|
const CreateWikiPageSchema = z.object({
|
|
189
192
|
slug: z.string().describe('URL slug for the page (without team prefix)'),
|
|
190
193
|
title: z.string().describe('Page title'),
|
|
191
|
-
content: z
|
|
194
|
+
content: z
|
|
195
|
+
.string()
|
|
196
|
+
.describe('Page content (markdown). Supports base64-encoded images (e.g., ) which will be auto-uploaded.'),
|
|
192
197
|
});
|
|
193
198
|
const UpdateWikiPageSchema = z.object({
|
|
194
199
|
page_id: z.string().describe('The page UUID'),
|
|
195
200
|
title: z.string().optional().describe('Updated title'),
|
|
196
|
-
content: z
|
|
201
|
+
content: z
|
|
202
|
+
.string()
|
|
203
|
+
.optional()
|
|
204
|
+
.describe('Updated content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
|
|
197
205
|
});
|
|
198
206
|
// Journal Entry schemas
|
|
199
207
|
const ListJournalEntriesSchema = z.object({
|
|
@@ -206,22 +214,42 @@ const GetJournalEntrySchema = z.object({
|
|
|
206
214
|
});
|
|
207
215
|
const CreateJournalEntrySchema = z.object({
|
|
208
216
|
title: z.string().describe('Entry title'),
|
|
209
|
-
content: z
|
|
217
|
+
content: z
|
|
218
|
+
.string()
|
|
219
|
+
.describe('Entry content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
|
|
210
220
|
date: z.string().optional().describe('Entry date (ISO format, defaults to now)'),
|
|
211
221
|
});
|
|
212
222
|
const UpdateJournalEntrySchema = z.object({
|
|
213
223
|
entry_id: z.string().describe('The entry UUID'),
|
|
214
224
|
title: z.string().optional().describe('Updated title'),
|
|
215
|
-
content: z
|
|
225
|
+
content: z
|
|
226
|
+
.string()
|
|
227
|
+
.optional()
|
|
228
|
+
.describe('Updated content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
|
|
216
229
|
});
|
|
217
230
|
// Comment schemas
|
|
218
231
|
const CreateWorkItemCommentSchema = z.object({
|
|
219
232
|
work_item_id: z.string().describe('The work item UUID'),
|
|
220
|
-
content: z
|
|
233
|
+
content: z
|
|
234
|
+
.string()
|
|
235
|
+
.describe('Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
|
|
221
236
|
});
|
|
222
237
|
const CreateIssueCommentSchema = z.object({
|
|
223
238
|
issue_id: z.string().describe('The issue UUID'),
|
|
224
|
-
content: z
|
|
239
|
+
content: z
|
|
240
|
+
.string()
|
|
241
|
+
.describe('Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.'),
|
|
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.'),
|
|
225
253
|
});
|
|
226
254
|
// Helper to detect if a work item description contains a plan
|
|
227
255
|
function hasPlan(description) {
|
|
@@ -531,7 +559,7 @@ class WolfpackMCPServer {
|
|
|
531
559
|
title: { type: 'string', description: 'Title of the work item' },
|
|
532
560
|
description: {
|
|
533
561
|
type: 'string',
|
|
534
|
-
description: 'Description/notes for the work item (markdown)',
|
|
562
|
+
description: 'Description/notes for the work item (markdown). Supports base64-encoded images which will be auto-uploaded.',
|
|
535
563
|
},
|
|
536
564
|
status: {
|
|
537
565
|
type: 'string',
|
|
@@ -648,7 +676,10 @@ class WolfpackMCPServer {
|
|
|
648
676
|
properties: {
|
|
649
677
|
slug: { type: 'string', description: 'URL slug for the page (without team prefix)' },
|
|
650
678
|
title: { type: 'string', description: 'Page title' },
|
|
651
|
-
content: {
|
|
679
|
+
content: {
|
|
680
|
+
type: 'string',
|
|
681
|
+
description: 'Page content (markdown). Supports base64-encoded images (e.g., ) which will be auto-uploaded.',
|
|
682
|
+
},
|
|
652
683
|
},
|
|
653
684
|
required: ['slug', 'title', 'content'],
|
|
654
685
|
},
|
|
@@ -661,7 +692,10 @@ class WolfpackMCPServer {
|
|
|
661
692
|
properties: {
|
|
662
693
|
page_id: { type: 'string', description: 'The page UUID' },
|
|
663
694
|
title: { type: 'string', description: 'Updated title' },
|
|
664
|
-
content: {
|
|
695
|
+
content: {
|
|
696
|
+
type: 'string',
|
|
697
|
+
description: 'Updated content (markdown). Supports base64-encoded images which will be auto-uploaded.',
|
|
698
|
+
},
|
|
665
699
|
},
|
|
666
700
|
required: ['page_id'],
|
|
667
701
|
},
|
|
@@ -700,7 +734,10 @@ class WolfpackMCPServer {
|
|
|
700
734
|
type: 'object',
|
|
701
735
|
properties: {
|
|
702
736
|
title: { type: 'string', description: 'Entry title' },
|
|
703
|
-
content: {
|
|
737
|
+
content: {
|
|
738
|
+
type: 'string',
|
|
739
|
+
description: 'Entry content (markdown). Supports base64-encoded images which will be auto-uploaded.',
|
|
740
|
+
},
|
|
704
741
|
date: { type: 'string', description: 'Entry date (ISO format, defaults to now)' },
|
|
705
742
|
},
|
|
706
743
|
required: ['title', 'content'],
|
|
@@ -714,7 +751,10 @@ class WolfpackMCPServer {
|
|
|
714
751
|
properties: {
|
|
715
752
|
entry_id: { type: 'string', description: 'The entry UUID' },
|
|
716
753
|
title: { type: 'string', description: 'Updated title' },
|
|
717
|
-
content: {
|
|
754
|
+
content: {
|
|
755
|
+
type: 'string',
|
|
756
|
+
description: 'Updated content (markdown). Supports base64-encoded images which will be auto-uploaded.',
|
|
757
|
+
},
|
|
718
758
|
},
|
|
719
759
|
required: ['entry_id'],
|
|
720
760
|
},
|
|
@@ -731,7 +771,10 @@ class WolfpackMCPServer {
|
|
|
731
771
|
type: 'object',
|
|
732
772
|
properties: {
|
|
733
773
|
work_item_id: { type: 'string', description: 'The work item UUID' },
|
|
734
|
-
content: {
|
|
774
|
+
content: {
|
|
775
|
+
type: 'string',
|
|
776
|
+
description: 'Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.',
|
|
777
|
+
},
|
|
735
778
|
},
|
|
736
779
|
required: ['work_item_id', 'content'],
|
|
737
780
|
},
|
|
@@ -743,11 +786,39 @@ class WolfpackMCPServer {
|
|
|
743
786
|
type: 'object',
|
|
744
787
|
properties: {
|
|
745
788
|
issue_id: { type: 'string', description: 'The issue UUID' },
|
|
746
|
-
content: {
|
|
789
|
+
content: {
|
|
790
|
+
type: 'string',
|
|
791
|
+
description: 'Comment content (markdown). Supports base64-encoded images which will be auto-uploaded.',
|
|
792
|
+
},
|
|
747
793
|
},
|
|
748
794
|
required: ['issue_id', 'content'],
|
|
749
795
|
},
|
|
750
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
|
+
},
|
|
751
822
|
],
|
|
752
823
|
}));
|
|
753
824
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -1163,6 +1234,23 @@ class WolfpackMCPServer {
|
|
|
1163
1234
|
],
|
|
1164
1235
|
};
|
|
1165
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
|
+
}
|
|
1166
1254
|
default:
|
|
1167
1255
|
throw new Error(`Unknown tool: ${name}`);
|
|
1168
1256
|
}
|
|
@@ -1176,6 +1264,18 @@ class WolfpackMCPServer {
|
|
|
1176
1264
|
}
|
|
1177
1265
|
});
|
|
1178
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
|
+
}
|
|
1179
1279
|
async start() {
|
|
1180
1280
|
// Start version check early (non-blocking)
|
|
1181
1281
|
const updateCheckPromise = checkForUpdates();
|
|
@@ -1198,6 +1298,7 @@ class WolfpackMCPServer {
|
|
|
1198
1298
|
if (teams.length === 1) {
|
|
1199
1299
|
// Auto-select the only team
|
|
1200
1300
|
this.client.setTeamId(teams[0].id);
|
|
1301
|
+
this.client.setTeamSlug(teams[0].slug);
|
|
1201
1302
|
console.error(`Auto-selected team: ${teams[0].name} (${teams[0].slug})`);
|
|
1202
1303
|
}
|
|
1203
1304
|
else if (teams.length === 0) {
|