@zzp123/mcp-zentao 1.4.0 → 1.4.2
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/README.md +78 -14
- package/dist/index.js +76 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
# MCP-Zentao
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@zzp123/mcp-zentao)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
3
6
|
禅道项目管理系统的高级API集成包,提供任务管理、Bug跟踪等功能的完整封装,专为Cursor IDE设计的MCP扩展。
|
|
4
7
|
|
|
5
|
-
## 安装
|
|
8
|
+
## 📦 安装
|
|
6
9
|
|
|
7
10
|
```bash
|
|
8
|
-
npm install @
|
|
11
|
+
npm install @zzp123/mcp-zentao -g
|
|
9
12
|
```
|
|
10
13
|
|
|
14
|
+
## 📋 版本历史
|
|
15
|
+
|
|
16
|
+
查看完整的版本更新历史,请访问 [CHANGELOG.md](./CHANGELOG.md)
|
|
17
|
+
|
|
18
|
+
**最新版本**: v1.4.2
|
|
19
|
+
- 创建完整的版本更新历史文档(CHANGELOG.md)
|
|
20
|
+
- 更新所有文档和示例代码
|
|
21
|
+
- 修正包名和API签名
|
|
22
|
+
|
|
23
|
+
**主要版本**:
|
|
24
|
+
- v1.4.1 - 修复 uploadImageFromClipboard 返回值问题
|
|
25
|
+
- v1.4.0 - 新增解决Bug接口
|
|
26
|
+
- v1.3.1 - 修复剪贴板图片上传核心问题
|
|
27
|
+
- v1.3.0 - 添加命令行脚本支持
|
|
28
|
+
- v1.2.0 - 添加文件上传/下载功能
|
|
29
|
+
|
|
11
30
|
## 使用方法
|
|
12
31
|
|
|
13
32
|
### 首次使用(配置禅道信息)
|
|
@@ -158,7 +177,7 @@ docker run -d \
|
|
|
158
177
|
## 基本使用
|
|
159
178
|
|
|
160
179
|
```typescript
|
|
161
|
-
import { ZentaoAPI } from '@
|
|
180
|
+
import { ZentaoAPI } from '@zzp123/mcp-zentao';
|
|
162
181
|
|
|
163
182
|
// 创建API实例
|
|
164
183
|
const api = new ZentaoAPI({
|
|
@@ -198,19 +217,34 @@ async function finishTask(taskId: number) {
|
|
|
198
217
|
}
|
|
199
218
|
}
|
|
200
219
|
|
|
201
|
-
// 解决Bug
|
|
220
|
+
// 解决Bug (v1.4.0+)
|
|
202
221
|
async function resolveBug(bugId: number) {
|
|
203
222
|
try {
|
|
204
|
-
await api.resolveBug(bugId, {
|
|
223
|
+
const result = await api.resolveBug(bugId, {
|
|
205
224
|
resolution: 'fixed',
|
|
206
225
|
resolvedBuild: 'trunk',
|
|
226
|
+
assignedTo: 'admin',
|
|
207
227
|
comment: '问题已修复'
|
|
208
228
|
});
|
|
209
|
-
console.log('Bug
|
|
229
|
+
console.log('Bug已解决:', result);
|
|
210
230
|
} catch (error) {
|
|
211
231
|
console.error('解决Bug失败:', error);
|
|
212
232
|
}
|
|
213
233
|
}
|
|
234
|
+
|
|
235
|
+
// 上传剪贴板图片 (v1.3.1+)
|
|
236
|
+
async function uploadClipboardImage() {
|
|
237
|
+
try {
|
|
238
|
+
// 注意:需要先复制图片到系统剪贴板
|
|
239
|
+
const result = await api.uploadFile({
|
|
240
|
+
filename: 'screenshot.png',
|
|
241
|
+
uid: 'optional-uid'
|
|
242
|
+
});
|
|
243
|
+
console.log('图片已上传:', result);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error('上传失败:', error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
214
248
|
```
|
|
215
249
|
|
|
216
250
|
## API文档
|
|
@@ -243,12 +277,22 @@ constructor(config: {
|
|
|
243
277
|
- 参数: taskId - 任务ID
|
|
244
278
|
- 返回: Promise<void>
|
|
245
279
|
|
|
246
|
-
4. `resolveBug(bugId: number, resolution:
|
|
280
|
+
4. `resolveBug(bugId: number, resolution: ResolveBugRequest): Promise<Bug>` (v1.4.0+)
|
|
247
281
|
- 解决指定ID的Bug
|
|
248
|
-
- 参数:
|
|
282
|
+
- 参数:
|
|
249
283
|
- bugId - Bug ID
|
|
250
284
|
- resolution - Bug解决方案
|
|
251
|
-
- 返回: Promise<
|
|
285
|
+
- 返回: Promise<Bug> - 返回更新后的Bug对象
|
|
286
|
+
|
|
287
|
+
5. `uploadFile(request: UploadFileRequest): Promise<FileUploadResponse>` (v1.2.0+)
|
|
288
|
+
- 上传文件到禅道
|
|
289
|
+
- 参数: UploadFileRequest - 文件上传请求
|
|
290
|
+
- 返回: Promise<FileUploadResponse> - 包含文件ID和访问URL
|
|
291
|
+
|
|
292
|
+
6. `downloadFile(fileId: number): Promise<Buffer>` (v1.2.0+)
|
|
293
|
+
- 从禅道下载文件
|
|
294
|
+
- 参数: fileId - 文件ID
|
|
295
|
+
- 返回: Promise<Buffer> - 文件的二进制数据
|
|
252
296
|
|
|
253
297
|
### 类型定义
|
|
254
298
|
|
|
@@ -258,6 +302,8 @@ interface Task {
|
|
|
258
302
|
name: string;
|
|
259
303
|
status: string;
|
|
260
304
|
pri: number;
|
|
305
|
+
assignedTo: string;
|
|
306
|
+
deadline: string;
|
|
261
307
|
// ... 其他任务属性
|
|
262
308
|
}
|
|
263
309
|
|
|
@@ -266,14 +312,32 @@ interface Bug {
|
|
|
266
312
|
title: string;
|
|
267
313
|
status: string;
|
|
268
314
|
severity: number;
|
|
315
|
+
resolvedBy: string;
|
|
316
|
+
resolution: string;
|
|
269
317
|
// ... 其他Bug属性
|
|
270
318
|
}
|
|
271
319
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
320
|
+
// v1.4.0+
|
|
321
|
+
type ResolutionType = 'fixed' | 'bydesign' | 'duplicate' | 'external' | 'notrepro' | 'postponed' | 'willnotfix';
|
|
322
|
+
|
|
323
|
+
interface ResolveBugRequest {
|
|
324
|
+
resolution: ResolutionType; // 解决方案(必填)
|
|
325
|
+
resolvedBuild?: string; // 解决版本(resolution='fixed'时必填)
|
|
326
|
+
assignedTo?: string; // 指派给
|
|
327
|
+
comment?: string; // 备注
|
|
328
|
+
duplicateBug?: number; // 重复Bug的ID(resolution='duplicate'时必填)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// v1.2.0+
|
|
332
|
+
interface UploadFileRequest {
|
|
333
|
+
file: Buffer; // 文件数据
|
|
334
|
+
filename: string; // 文件名
|
|
335
|
+
uid?: string; // 唯一标识符(可选)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
interface FileUploadResponse {
|
|
339
|
+
id: number; // 文件ID
|
|
340
|
+
url: string; // 文件访问URL
|
|
277
341
|
}
|
|
278
342
|
```
|
|
279
343
|
|
package/dist/index.js
CHANGED
|
@@ -999,18 +999,26 @@ server.tool("uploadImageFromClipboard", {
|
|
|
999
999
|
filename: z.string().optional(),
|
|
1000
1000
|
uid: z.string().optional()
|
|
1001
1001
|
}, async ({ filename, uid }) => {
|
|
1002
|
-
if (!zentaoApi)
|
|
1003
|
-
|
|
1002
|
+
if (!zentaoApi) {
|
|
1003
|
+
return {
|
|
1004
|
+
content: [{
|
|
1005
|
+
type: "text",
|
|
1006
|
+
text: JSON.stringify({ error: "请先初始化禅道API配置" }, null, 2)
|
|
1007
|
+
}],
|
|
1008
|
+
isError: true
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1004
1011
|
const fs = await import('fs');
|
|
1005
1012
|
const path = await import('path');
|
|
1006
1013
|
const { execSync } = await import('child_process');
|
|
1007
1014
|
try {
|
|
1015
|
+
console.log('[uploadImageFromClipboard] 开始执行...');
|
|
1008
1016
|
// 读取系统剪贴板图片
|
|
1009
|
-
let base64Data;
|
|
1010
1017
|
let fileBuffer;
|
|
1011
1018
|
let finalFilename;
|
|
1012
1019
|
// Windows: 使用 PowerShell 读取剪贴板
|
|
1013
1020
|
if (process.platform === 'win32') {
|
|
1021
|
+
console.log('[uploadImageFromClipboard] Windows平台,使用PowerShell读取剪贴板...');
|
|
1014
1022
|
const psScript = `
|
|
1015
1023
|
Add-Type -AssemblyName System.Windows.Forms
|
|
1016
1024
|
$img = [System.Windows.Forms.Clipboard]::GetImage()
|
|
@@ -1028,64 +1036,118 @@ server.tool("uploadImageFromClipboard", {
|
|
|
1028
1036
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
1029
1037
|
}).trim();
|
|
1030
1038
|
if (!result || result.includes('NoImage') || result.includes('Error')) {
|
|
1031
|
-
|
|
1039
|
+
console.error('[uploadImageFromClipboard] 剪贴板中没有图片');
|
|
1040
|
+
return {
|
|
1041
|
+
content: [{
|
|
1042
|
+
type: "text",
|
|
1043
|
+
text: JSON.stringify({
|
|
1044
|
+
error: "剪贴板中没有图片",
|
|
1045
|
+
tip: "请先复制图片,不要粘贴到输入框,直接告诉我'上传'"
|
|
1046
|
+
}, null, 2)
|
|
1047
|
+
}],
|
|
1048
|
+
isError: true
|
|
1049
|
+
};
|
|
1032
1050
|
}
|
|
1033
|
-
|
|
1034
|
-
fileBuffer = Buffer.from(base64Data, 'base64');
|
|
1051
|
+
fileBuffer = Buffer.from(result, 'base64');
|
|
1035
1052
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1053
|
+
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
1036
1054
|
}
|
|
1037
1055
|
// macOS: 使用 pngpaste
|
|
1038
1056
|
else if (process.platform === 'darwin') {
|
|
1057
|
+
console.log('[uploadImageFromClipboard] macOS平台,使用pngpaste读取剪贴板...');
|
|
1039
1058
|
const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
|
|
1040
1059
|
try {
|
|
1041
1060
|
execSync(`pngpaste "${tempFile}"`);
|
|
1042
1061
|
fileBuffer = fs.readFileSync(tempFile);
|
|
1043
1062
|
fs.unlinkSync(tempFile);
|
|
1044
1063
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1064
|
+
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
1045
1065
|
}
|
|
1046
|
-
catch {
|
|
1047
|
-
|
|
1066
|
+
catch (err) {
|
|
1067
|
+
console.error('[uploadImageFromClipboard] macOS读取剪贴板失败:', err);
|
|
1068
|
+
return {
|
|
1069
|
+
content: [{
|
|
1070
|
+
type: "text",
|
|
1071
|
+
text: JSON.stringify({
|
|
1072
|
+
error: "剪贴板中没有图片或pngpaste未安装",
|
|
1073
|
+
tip: "请先安装 pngpaste: brew install pngpaste"
|
|
1074
|
+
}, null, 2)
|
|
1075
|
+
}],
|
|
1076
|
+
isError: true
|
|
1077
|
+
};
|
|
1048
1078
|
}
|
|
1049
1079
|
}
|
|
1050
1080
|
// Linux: 使用 xclip
|
|
1051
1081
|
else {
|
|
1082
|
+
console.log('[uploadImageFromClipboard] Linux平台,使用xclip读取剪贴板...');
|
|
1052
1083
|
try {
|
|
1053
1084
|
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
1054
1085
|
maxBuffer: 10 * 1024 * 1024
|
|
1055
1086
|
});
|
|
1056
1087
|
fileBuffer = buffer;
|
|
1057
1088
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1089
|
+
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
1058
1090
|
}
|
|
1059
|
-
catch {
|
|
1060
|
-
|
|
1091
|
+
catch (err) {
|
|
1092
|
+
console.error('[uploadImageFromClipboard] Linux读取剪贴板失败:', err);
|
|
1093
|
+
return {
|
|
1094
|
+
content: [{
|
|
1095
|
+
type: "text",
|
|
1096
|
+
text: JSON.stringify({
|
|
1097
|
+
error: "剪贴板中没有图片或xclip未安装",
|
|
1098
|
+
tip: "请先安装 xclip: sudo apt-get install xclip"
|
|
1099
|
+
}, null, 2)
|
|
1100
|
+
}],
|
|
1101
|
+
isError: true
|
|
1102
|
+
};
|
|
1061
1103
|
}
|
|
1062
1104
|
}
|
|
1063
1105
|
// 创建 img 文件夹
|
|
1064
1106
|
const imgDir = path.join(process.cwd(), 'img');
|
|
1065
1107
|
if (!fs.existsSync(imgDir)) {
|
|
1108
|
+
console.log(`[uploadImageFromClipboard] 创建目录: ${imgDir}`);
|
|
1066
1109
|
fs.mkdirSync(imgDir, { recursive: true });
|
|
1067
1110
|
}
|
|
1068
1111
|
// 保存到 img 文件夹
|
|
1069
1112
|
const savedPath = path.join(imgDir, finalFilename);
|
|
1070
1113
|
fs.writeFileSync(savedPath, fileBuffer);
|
|
1114
|
+
console.log(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}`);
|
|
1071
1115
|
// 上传到禅道
|
|
1072
|
-
|
|
1116
|
+
console.log('[uploadImageFromClipboard] 开始上传到禅道...');
|
|
1117
|
+
const uploadResult = await zentaoApi.uploadFile({
|
|
1073
1118
|
file: fileBuffer,
|
|
1074
1119
|
filename: finalFilename,
|
|
1075
1120
|
uid
|
|
1076
1121
|
});
|
|
1122
|
+
console.log('[uploadImageFromClipboard] 上传成功,结果:', uploadResult);
|
|
1077
1123
|
const response = {
|
|
1078
|
-
|
|
1124
|
+
success: true,
|
|
1125
|
+
upload: uploadResult,
|
|
1079
1126
|
savedPath: savedPath,
|
|
1127
|
+
filename: finalFilename,
|
|
1128
|
+
fileSize: fileBuffer.length,
|
|
1080
1129
|
message: `图片已保存到本地并上传到禅道`
|
|
1081
1130
|
};
|
|
1082
|
-
|
|
1131
|
+
console.log('[uploadImageFromClipboard] 返回结果:', response);
|
|
1132
|
+
return {
|
|
1133
|
+
content: [{
|
|
1134
|
+
type: "text",
|
|
1135
|
+
text: JSON.stringify(response, null, 2)
|
|
1136
|
+
}]
|
|
1137
|
+
};
|
|
1083
1138
|
}
|
|
1084
1139
|
catch (error) {
|
|
1140
|
+
console.error('[uploadImageFromClipboard] 发生错误:', error);
|
|
1141
|
+
const errorResponse = {
|
|
1142
|
+
success: false,
|
|
1143
|
+
error: error.message || String(error),
|
|
1144
|
+
stack: error.stack,
|
|
1145
|
+
tip: "请检查:\n1. 确保已复制图片到剪贴板\n2. 不要粘贴到输入框,直接说'上传剪贴板图片'\n3. 或使用命令行: upload.bat"
|
|
1146
|
+
};
|
|
1085
1147
|
return {
|
|
1086
1148
|
content: [{
|
|
1087
1149
|
type: "text",
|
|
1088
|
-
text:
|
|
1150
|
+
text: JSON.stringify(errorResponse, null, 2)
|
|
1089
1151
|
}],
|
|
1090
1152
|
isError: true
|
|
1091
1153
|
};
|