@zzp123/mcp-zentao 1.3.2 → 1.4.1
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/api/zentaoApi.d.ts +2 -1
- package/dist/api/zentaoApi.js +19 -0
- package/dist/index.js +99 -14
- package/dist/types/zentao.d.ts +8 -0
- package/package.json +1 -1
package/dist/api/zentaoApi.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AssignFeedbackRequest, Bug, BugStatus, Build, CloseFeedbackRequest, CreateBuildRequest, CreateBugRequest, CreateExecutionRequest, CreateFeedbackRequest, CreatePlanRequest, CreateProductRequest, CreateProgramRequest, CreateProjectRequest, CreateStoryRequest, CreateTaskRequest, CreateTestCaseRequest, CreateTicketRequest, CreateUserRequest, Execution, Feedback, FileUploadResponse, Module, ModuleType, Plan, Product, Program, Project, Release, Story, Task, TaskStatus, TaskUpdate, TestCase, Ticket, UpdateBuildRequest, UpdateBugRequest, UpdateExecutionRequest, UpdateFeedbackRequest, UpdatePlanRequest, UpdateProductRequest, UpdateProgramRequest, UpdateProjectRequest, UpdateStoryRequest, UpdateTestCaseRequest, UpdateTicketRequest, UpdateUserRequest, UploadFileRequest, UserDetail, ZentaoConfig } from '../types/zentao';
|
|
1
|
+
import { AssignFeedbackRequest, Bug, BugStatus, Build, CloseFeedbackRequest, CreateBuildRequest, CreateBugRequest, CreateExecutionRequest, CreateFeedbackRequest, CreatePlanRequest, CreateProductRequest, CreateProgramRequest, CreateProjectRequest, CreateStoryRequest, CreateTaskRequest, CreateTestCaseRequest, CreateTicketRequest, CreateUserRequest, Execution, Feedback, FileUploadResponse, Module, ModuleType, Plan, Product, Program, Project, Release, ResolveBugRequest, Story, Task, TaskStatus, TaskUpdate, TestCase, Ticket, UpdateBuildRequest, UpdateBugRequest, UpdateExecutionRequest, UpdateFeedbackRequest, UpdatePlanRequest, UpdateProductRequest, UpdateProgramRequest, UpdateProjectRequest, UpdateStoryRequest, UpdateTestCaseRequest, UpdateTicketRequest, UpdateUserRequest, UploadFileRequest, UserDetail, ZentaoConfig } from '../types/zentao';
|
|
2
2
|
export declare class ZentaoAPI {
|
|
3
3
|
private config;
|
|
4
4
|
private client;
|
|
@@ -11,6 +11,7 @@ export declare class ZentaoAPI {
|
|
|
11
11
|
getProducts(): Promise<Product[]>;
|
|
12
12
|
getMyBugs(status?: BugStatus, productId?: number): Promise<Bug[]>;
|
|
13
13
|
getBugDetail(bugId: number): Promise<Bug>;
|
|
14
|
+
resolveBug(bugId: number, resolution: ResolveBugRequest): Promise<Bug>;
|
|
14
15
|
updateTask(taskId: number, update: TaskUpdate): Promise<Task>;
|
|
15
16
|
finishTask(taskId: number, update?: TaskUpdate): Promise<Task>;
|
|
16
17
|
createTask(task: CreateTaskRequest): Promise<Task>;
|
package/dist/api/zentaoApi.js
CHANGED
|
@@ -158,6 +158,25 @@ export class ZentaoAPI {
|
|
|
158
158
|
throw error;
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
+
async resolveBug(bugId, resolution) {
|
|
162
|
+
try {
|
|
163
|
+
console.log(`正在解决Bug ${bugId}...`);
|
|
164
|
+
// 验证必填字段
|
|
165
|
+
if (resolution.resolution === 'fixed' && !resolution.resolvedBuild) {
|
|
166
|
+
throw new Error('当解决方案为"已解决(fixed)"时,必须提供解决版本(resolvedBuild)');
|
|
167
|
+
}
|
|
168
|
+
if (resolution.resolution === 'duplicate' && !resolution.duplicateBug) {
|
|
169
|
+
throw new Error('当解决方案为"重复Bug(duplicate)"时,必须提供重复Bug的ID(duplicateBug)');
|
|
170
|
+
}
|
|
171
|
+
const response = await this.request('POST', `/bugs/${bugId}/resolve`, undefined, resolution);
|
|
172
|
+
console.log('Bug解决响应:', response);
|
|
173
|
+
return response;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.error('解决Bug失败:', error);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
161
180
|
async updateTask(taskId, update) {
|
|
162
181
|
try {
|
|
163
182
|
console.log(`正在更新任务 ${taskId}...`);
|
package/dist/index.js
CHANGED
|
@@ -109,6 +109,29 @@ server.tool("getBugDetail", {
|
|
|
109
109
|
content: [{ type: "text", text: JSON.stringify(bug, null, 2) }]
|
|
110
110
|
};
|
|
111
111
|
});
|
|
112
|
+
// Add resolveBug tool
|
|
113
|
+
server.tool("resolveBug", {
|
|
114
|
+
bugId: z.number(),
|
|
115
|
+
resolution: z.enum(['fixed', 'bydesign', 'duplicate', 'external', 'notrepro', 'postponed', 'willnotfix']),
|
|
116
|
+
resolvedBuild: z.string().optional(),
|
|
117
|
+
assignedTo: z.string().optional(),
|
|
118
|
+
comment: z.string().optional(),
|
|
119
|
+
duplicateBug: z.number().optional()
|
|
120
|
+
}, async ({ bugId, resolution, resolvedBuild, assignedTo, comment, duplicateBug }) => {
|
|
121
|
+
if (!zentaoApi)
|
|
122
|
+
throw new Error("Please initialize Zentao API first");
|
|
123
|
+
const resolveBugRequest = {
|
|
124
|
+
resolution,
|
|
125
|
+
resolvedBuild,
|
|
126
|
+
assignedTo,
|
|
127
|
+
comment,
|
|
128
|
+
duplicateBug
|
|
129
|
+
};
|
|
130
|
+
const bug = await zentaoApi.resolveBug(bugId, resolveBugRequest);
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: "text", text: JSON.stringify(bug, null, 2) }]
|
|
133
|
+
};
|
|
134
|
+
});
|
|
112
135
|
// Add updateTask tool
|
|
113
136
|
server.tool("updateTask", {
|
|
114
137
|
taskId: z.number(),
|
|
@@ -976,18 +999,26 @@ server.tool("uploadImageFromClipboard", {
|
|
|
976
999
|
filename: z.string().optional(),
|
|
977
1000
|
uid: z.string().optional()
|
|
978
1001
|
}, async ({ filename, uid }) => {
|
|
979
|
-
if (!zentaoApi)
|
|
980
|
-
|
|
1002
|
+
if (!zentaoApi) {
|
|
1003
|
+
return {
|
|
1004
|
+
content: [{
|
|
1005
|
+
type: "text",
|
|
1006
|
+
text: JSON.stringify({ error: "请先初始化禅道API配置" }, null, 2)
|
|
1007
|
+
}],
|
|
1008
|
+
isError: true
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
981
1011
|
const fs = await import('fs');
|
|
982
1012
|
const path = await import('path');
|
|
983
1013
|
const { execSync } = await import('child_process');
|
|
984
1014
|
try {
|
|
1015
|
+
console.log('[uploadImageFromClipboard] 开始执行...');
|
|
985
1016
|
// 读取系统剪贴板图片
|
|
986
|
-
let base64Data;
|
|
987
1017
|
let fileBuffer;
|
|
988
1018
|
let finalFilename;
|
|
989
1019
|
// Windows: 使用 PowerShell 读取剪贴板
|
|
990
1020
|
if (process.platform === 'win32') {
|
|
1021
|
+
console.log('[uploadImageFromClipboard] Windows平台,使用PowerShell读取剪贴板...');
|
|
991
1022
|
const psScript = `
|
|
992
1023
|
Add-Type -AssemblyName System.Windows.Forms
|
|
993
1024
|
$img = [System.Windows.Forms.Clipboard]::GetImage()
|
|
@@ -1005,64 +1036,118 @@ server.tool("uploadImageFromClipboard", {
|
|
|
1005
1036
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
1006
1037
|
}).trim();
|
|
1007
1038
|
if (!result || result.includes('NoImage') || result.includes('Error')) {
|
|
1008
|
-
|
|
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
|
+
};
|
|
1009
1050
|
}
|
|
1010
|
-
|
|
1011
|
-
fileBuffer = Buffer.from(base64Data, 'base64');
|
|
1051
|
+
fileBuffer = Buffer.from(result, 'base64');
|
|
1012
1052
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1053
|
+
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
1013
1054
|
}
|
|
1014
1055
|
// macOS: 使用 pngpaste
|
|
1015
1056
|
else if (process.platform === 'darwin') {
|
|
1057
|
+
console.log('[uploadImageFromClipboard] macOS平台,使用pngpaste读取剪贴板...');
|
|
1016
1058
|
const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
|
|
1017
1059
|
try {
|
|
1018
1060
|
execSync(`pngpaste "${tempFile}"`);
|
|
1019
1061
|
fileBuffer = fs.readFileSync(tempFile);
|
|
1020
1062
|
fs.unlinkSync(tempFile);
|
|
1021
1063
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1064
|
+
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
1022
1065
|
}
|
|
1023
|
-
catch {
|
|
1024
|
-
|
|
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
|
+
};
|
|
1025
1078
|
}
|
|
1026
1079
|
}
|
|
1027
1080
|
// Linux: 使用 xclip
|
|
1028
1081
|
else {
|
|
1082
|
+
console.log('[uploadImageFromClipboard] Linux平台,使用xclip读取剪贴板...');
|
|
1029
1083
|
try {
|
|
1030
1084
|
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
1031
1085
|
maxBuffer: 10 * 1024 * 1024
|
|
1032
1086
|
});
|
|
1033
1087
|
fileBuffer = buffer;
|
|
1034
1088
|
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
1089
|
+
console.log(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes`);
|
|
1035
1090
|
}
|
|
1036
|
-
catch {
|
|
1037
|
-
|
|
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
|
+
};
|
|
1038
1103
|
}
|
|
1039
1104
|
}
|
|
1040
1105
|
// 创建 img 文件夹
|
|
1041
1106
|
const imgDir = path.join(process.cwd(), 'img');
|
|
1042
1107
|
if (!fs.existsSync(imgDir)) {
|
|
1108
|
+
console.log(`[uploadImageFromClipboard] 创建目录: ${imgDir}`);
|
|
1043
1109
|
fs.mkdirSync(imgDir, { recursive: true });
|
|
1044
1110
|
}
|
|
1045
1111
|
// 保存到 img 文件夹
|
|
1046
1112
|
const savedPath = path.join(imgDir, finalFilename);
|
|
1047
1113
|
fs.writeFileSync(savedPath, fileBuffer);
|
|
1114
|
+
console.log(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}`);
|
|
1048
1115
|
// 上传到禅道
|
|
1049
|
-
|
|
1116
|
+
console.log('[uploadImageFromClipboard] 开始上传到禅道...');
|
|
1117
|
+
const uploadResult = await zentaoApi.uploadFile({
|
|
1050
1118
|
file: fileBuffer,
|
|
1051
1119
|
filename: finalFilename,
|
|
1052
1120
|
uid
|
|
1053
1121
|
});
|
|
1122
|
+
console.log('[uploadImageFromClipboard] 上传成功,结果:', uploadResult);
|
|
1054
1123
|
const response = {
|
|
1055
|
-
|
|
1124
|
+
success: true,
|
|
1125
|
+
upload: uploadResult,
|
|
1056
1126
|
savedPath: savedPath,
|
|
1127
|
+
filename: finalFilename,
|
|
1128
|
+
fileSize: fileBuffer.length,
|
|
1057
1129
|
message: `图片已保存到本地并上传到禅道`
|
|
1058
1130
|
};
|
|
1059
|
-
|
|
1131
|
+
console.log('[uploadImageFromClipboard] 返回结果:', response);
|
|
1132
|
+
return {
|
|
1133
|
+
content: [{
|
|
1134
|
+
type: "text",
|
|
1135
|
+
text: JSON.stringify(response, null, 2)
|
|
1136
|
+
}]
|
|
1137
|
+
};
|
|
1060
1138
|
}
|
|
1061
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
|
+
};
|
|
1062
1147
|
return {
|
|
1063
1148
|
content: [{
|
|
1064
1149
|
type: "text",
|
|
1065
|
-
text:
|
|
1150
|
+
text: JSON.stringify(errorResponse, null, 2)
|
|
1066
1151
|
}],
|
|
1067
1152
|
isError: true
|
|
1068
1153
|
};
|
package/dist/types/zentao.d.ts
CHANGED
|
@@ -50,6 +50,14 @@ export interface TaskUpdate {
|
|
|
50
50
|
}
|
|
51
51
|
export type TaskStatus = 'wait' | 'doing' | 'done' | 'all';
|
|
52
52
|
export type BugStatus = 'active' | 'resolved' | 'closed' | 'all';
|
|
53
|
+
export type ResolutionType = 'fixed' | 'bydesign' | 'duplicate' | 'external' | 'notrepro' | 'postponed' | 'willnotfix';
|
|
54
|
+
export interface ResolveBugRequest {
|
|
55
|
+
resolution: ResolutionType;
|
|
56
|
+
resolvedBuild?: string;
|
|
57
|
+
assignedTo?: string;
|
|
58
|
+
comment?: string;
|
|
59
|
+
duplicateBug?: number;
|
|
60
|
+
}
|
|
53
61
|
export interface User {
|
|
54
62
|
id: number;
|
|
55
63
|
account: string;
|