@zzp123/mcp-zentao 1.18.9 → 1.18.10
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/CHANGELOG.md +1018 -1018
- package/LICENSE +20 -20
- package/dist/api/zentaoApi.js +19 -18
- package/dist/index-dev.js +43 -36
- package/dist/index-pm.js +69 -710
- package/dist/index-qa.js +29 -34
- package/dist/index.js +61 -41
- package/dist/roleConfig.d.ts +0 -4
- package/dist/roleConfig.js +24 -33
- package/dist/types/zentao.d.ts +1 -0
- package/json-args.js +26 -26
- package/package.json +75 -75
- package/scripts/MCP-INTEGRATION.md +298 -298
- package/scripts/README.md +315 -315
- package/scripts/generate-role-versions.cjs +212 -209
- package/scripts/get-clipboard-base64.js +93 -93
- package/scripts/get_clipboard.ps1 +13 -13
- package/scripts/upload-clipboard-image.js +200 -200
- package/scripts/upload-clipboard-image.ps1 +128 -128
- package/scripts/upload-clipboard-image.py +196 -196
- package/scripts/upload.bat.example +41 -41
package/dist/index-pm.js
CHANGED
|
@@ -17,15 +17,12 @@ if (configIndex !== -1 && configIndex + 1 < args.length) {
|
|
|
17
17
|
// 获取 --config 后面的 JSON 字符串并解析
|
|
18
18
|
const jsonStr = args[configIndex + 1];
|
|
19
19
|
configData = JSON.parse(jsonStr);
|
|
20
|
-
process.stdout.write('成功解析配置数据: ' + JSON.stringify(configData) + '\n');
|
|
21
20
|
// 如果配置数据中包含 config 对象,则保存配置
|
|
22
21
|
if (configData.config) {
|
|
23
|
-
process.stdout.write('正在保存配置...\n');
|
|
24
22
|
saveConfig(configData.config);
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
catch (error) {
|
|
28
|
-
process.stderr.write('配置解析失败: ' + String(error) + '\n');
|
|
29
26
|
process.exit(1);
|
|
30
27
|
}
|
|
31
28
|
}
|
|
@@ -36,11 +33,10 @@ const server = new McpServer({
|
|
|
36
33
|
});
|
|
37
34
|
// Initialize ZentaoAPI instance
|
|
38
35
|
let zentaoApi = null;
|
|
36
|
+
const getApi = () => zentaoApi;
|
|
39
37
|
export default async function main(params) {
|
|
40
|
-
process.stdout.write('接收到的参数: ' + JSON.stringify(params) + '\n');
|
|
41
38
|
// 如果传入了配置信息,就保存它
|
|
42
39
|
if (params.config) {
|
|
43
|
-
process.stdout.write('保存新的配置信息...\n');
|
|
44
40
|
saveConfig(params.config);
|
|
45
41
|
}
|
|
46
42
|
}
|
|
@@ -58,147 +54,6 @@ server.tool("initZentao", {}, async ({}) => {
|
|
|
58
54
|
content: [{ type: "text", text: JSON.stringify(config, null, 2) }]
|
|
59
55
|
};
|
|
60
56
|
});
|
|
61
|
-
server.tool("getMyTasks", {
|
|
62
|
-
status: z.enum(['wait', 'doing', 'done', 'all']).optional()
|
|
63
|
-
}, async ({ status }) => {
|
|
64
|
-
if (!zentaoApi)
|
|
65
|
-
throw new Error("Please initialize Zentao API first");
|
|
66
|
-
const tasks = await zentaoApi.getMyTasks(status);
|
|
67
|
-
return {
|
|
68
|
-
content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }]
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
server.tool("getTaskDetail", {
|
|
72
|
-
taskId: z.number()
|
|
73
|
-
}, async ({ taskId }) => {
|
|
74
|
-
if (!zentaoApi)
|
|
75
|
-
throw new Error("Please initialize Zentao API first");
|
|
76
|
-
const task = await zentaoApi.getTaskDetail(taskId);
|
|
77
|
-
return {
|
|
78
|
-
content: [{ type: "text", text: JSON.stringify(task, null, 2) }]
|
|
79
|
-
};
|
|
80
|
-
});
|
|
81
|
-
server.tool("getProducts", "获取产品列表 - 只返回核心字段(id, 名称, 负责人)", {
|
|
82
|
-
fields: z.enum(['basic', 'default', 'full']).optional().describe("返回字段级别:'basic'(id/名称/状态)、'default'(+负责人/创建人,默认)、'full'(完整字段)")
|
|
83
|
-
}, async ({ fields }) => {
|
|
84
|
-
if (!zentaoApi)
|
|
85
|
-
throw new Error("Please initialize Zentao API first");
|
|
86
|
-
const products = await zentaoApi.getProducts(fields);
|
|
87
|
-
return {
|
|
88
|
-
content: [{ type: "text", text: JSON.stringify(products, null, 2) }]
|
|
89
|
-
};
|
|
90
|
-
});
|
|
91
|
-
server.tool("getMyBugs", "获取我的Bug列表 - 固定查询指派给我的Bug(status='assigntome'),固定每页20条", {
|
|
92
|
-
productId: z.number().optional().describe("产品ID,默认使用第二个产品"),
|
|
93
|
-
page: z.number().optional().describe("页码,从1开始,默认为1"),
|
|
94
|
-
branch: z.string().optional().describe("分支筛选:'all'(所有分支,默认)、'0'(主干分支)、分支ID"),
|
|
95
|
-
order: z.string().optional().describe("排序方式,如 id_desc(ID降序), pri_desc(优先级降序), openedDate_desc(创建时间降序)等,默认id_desc"),
|
|
96
|
-
fields: z.enum(['basic', 'default', 'full']).optional().describe("返回字段级别:'basic'(基本字段,默认)、'default'(默认字段)、'full'(完整字段),默认'basic'")
|
|
97
|
-
}, async ({ productId, page, branch, order, fields }) => {
|
|
98
|
-
if (!zentaoApi)
|
|
99
|
-
throw new Error("Please initialize Zentao API first");
|
|
100
|
-
const result = await zentaoApi.getMyBugs(productId, page, branch, order, fields);
|
|
101
|
-
// 返回分页信息和数据
|
|
102
|
-
const summary = {
|
|
103
|
-
summary: `当前第 ${result.page} 页,共 ${result.total} 个Bug,本页显示 ${result.bugs.length} 个`,
|
|
104
|
-
pagination: {
|
|
105
|
-
page: result.page,
|
|
106
|
-
limit: result.limit,
|
|
107
|
-
total: result.total,
|
|
108
|
-
totalPages: Math.ceil(result.total / result.limit)
|
|
109
|
-
},
|
|
110
|
-
bugs: result.bugs
|
|
111
|
-
};
|
|
112
|
-
return {
|
|
113
|
-
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
114
|
-
};
|
|
115
|
-
});
|
|
116
|
-
server.tool("getProductBugs", "获取指定产品的Bug列表 - 支持多种状态筛选和分支筛选", {
|
|
117
|
-
productId: z.number().describe("产品ID(必填)"),
|
|
118
|
-
page: z.number().optional().describe("页码,从1开始,默认为1"),
|
|
119
|
-
limit: z.number().optional().describe("每页数量,默认20,最大100"),
|
|
120
|
-
status: z.enum([
|
|
121
|
-
'active', // 激活状态
|
|
122
|
-
'resolved', // 已解决
|
|
123
|
-
'closed', // 已关闭
|
|
124
|
-
'all', // 所有状态
|
|
125
|
-
'assigntome', // 指派给我的
|
|
126
|
-
'openedbyme', // 由我创建
|
|
127
|
-
'resolvedbyme', // 由我解决
|
|
128
|
-
'assignedbyme', // 由我指派
|
|
129
|
-
'assigntonull', // 未指派
|
|
130
|
-
'unconfirmed', // 未确认
|
|
131
|
-
'unclosed', // 未关闭
|
|
132
|
-
'unresolved', // 未解决(激活状态)
|
|
133
|
-
'toclosed', // 待关闭(已解决)
|
|
134
|
-
'postponedbugs', // 延期的Bug
|
|
135
|
-
'longlifebugs', // 长期未处理的Bug
|
|
136
|
-
'overduebugs', // 已过期的Bug
|
|
137
|
-
'review', // 待我审核
|
|
138
|
-
'feedback', // 用户反馈
|
|
139
|
-
'needconfirm', // 需求变更需确认
|
|
140
|
-
'bysearch' // 自定义搜索
|
|
141
|
-
]).optional().describe("Bug状态筛选,默认返回所有状态"),
|
|
142
|
-
branch: z.string().optional().describe("分支筛选:'all'(所有分支,默认)、'0'(主干分支)、分支ID"),
|
|
143
|
-
order: z.string().optional().describe("排序方式,如 id_desc(ID降序), pri_desc(优先级降序), openedDate_desc(创建时间降序)等,默认id_desc"),
|
|
144
|
-
fields: z.enum(['basic', 'default', 'full']).optional().describe("返回字段级别:'basic'(基本字段,默认)、'default'(默认字段)、'full'(完整字段),默认'basic'")
|
|
145
|
-
}, async ({ productId, page, limit, status, branch, order, fields }) => {
|
|
146
|
-
if (!zentaoApi)
|
|
147
|
-
throw new Error("Please initialize Zentao API first");
|
|
148
|
-
const result = await zentaoApi.getProductBugs(productId, page, limit, status, branch, order, fields);
|
|
149
|
-
// 返回分页信息和数据
|
|
150
|
-
const summary = {
|
|
151
|
-
summary: `当前第 ${result.page} 页,共 ${result.total} 个Bug,本页显示 ${result.bugs.length} 个`,
|
|
152
|
-
pagination: {
|
|
153
|
-
page: result.page,
|
|
154
|
-
limit: result.limit,
|
|
155
|
-
total: result.total,
|
|
156
|
-
totalPages: Math.ceil(result.total / result.limit)
|
|
157
|
-
},
|
|
158
|
-
bugs: result.bugs
|
|
159
|
-
};
|
|
160
|
-
return {
|
|
161
|
-
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
server.tool("getBugDetail", {
|
|
165
|
-
bugId: z.number(),
|
|
166
|
-
fields: z.enum(['basic', 'detail', 'full']).optional().describe(`
|
|
167
|
-
字段级别:
|
|
168
|
-
- basic: 基本信息(id, title, status, severity, pri等核心字段)
|
|
169
|
-
- detail: 详细信息(包含steps步骤,但不包含files/cases/linkBugs等关联数据)
|
|
170
|
-
- full: 完整信息(包含所有字段,默认值)
|
|
171
|
-
`.trim())
|
|
172
|
-
}, async ({ bugId, fields }) => {
|
|
173
|
-
if (!zentaoApi)
|
|
174
|
-
throw new Error("Please initialize Zentao API first");
|
|
175
|
-
const bug = await zentaoApi.getBugDetail(bugId, fields);
|
|
176
|
-
// 添加说明信息
|
|
177
|
-
const result = {
|
|
178
|
-
fieldLevel: fields || 'full',
|
|
179
|
-
bug: bug
|
|
180
|
-
};
|
|
181
|
-
// 根据字段级别添加提示
|
|
182
|
-
if (fields === 'basic') {
|
|
183
|
-
result.note = '仅显示基本信息,如需完整内容请使用 fields=detail 或 fields=full';
|
|
184
|
-
}
|
|
185
|
-
else if (fields === 'detail') {
|
|
186
|
-
result.note = '显示详细信息但不包含关联数据(files/cases/linkBugs),如需完整内容请使用 fields=full';
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
190
|
-
};
|
|
191
|
-
});
|
|
192
|
-
server.tool("getPrograms", {
|
|
193
|
-
order: z.string().optional()
|
|
194
|
-
}, async ({ order }) => {
|
|
195
|
-
if (!zentaoApi)
|
|
196
|
-
throw new Error("Please initialize Zentao API first");
|
|
197
|
-
const programs = await zentaoApi.getPrograms(order);
|
|
198
|
-
return {
|
|
199
|
-
content: [{ type: "text", text: JSON.stringify(programs, null, 2) }]
|
|
200
|
-
};
|
|
201
|
-
});
|
|
202
57
|
server.tool("getProductPlans", {
|
|
203
58
|
productId: z.number(),
|
|
204
59
|
branch: z.number().optional(),
|
|
@@ -210,8 +65,19 @@ server.tool("getProductPlans", {
|
|
|
210
65
|
if (!zentaoApi)
|
|
211
66
|
throw new Error("Please initialize Zentao API first");
|
|
212
67
|
const plans = await zentaoApi.getProductPlans(productId, branch, begin, end, status, parent);
|
|
68
|
+
// 精简返回字段
|
|
69
|
+
const simplifyPlan = (p) => ({
|
|
70
|
+
id: p.id,
|
|
71
|
+
title: p.title,
|
|
72
|
+
status: p.status,
|
|
73
|
+
begin: p.begin,
|
|
74
|
+
end: p.end,
|
|
75
|
+
stories: p.stories,
|
|
76
|
+
bugs: p.bugs
|
|
77
|
+
});
|
|
78
|
+
const simplified = plans.map(simplifyPlan);
|
|
213
79
|
return {
|
|
214
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
80
|
+
content: [{ type: "text", text: JSON.stringify(simplified, null, 2) }]
|
|
215
81
|
};
|
|
216
82
|
});
|
|
217
83
|
server.tool("createStory", "创建需求 - 支持创建软件需求(story)或用户需求(requirement)", {
|
|
@@ -246,33 +112,6 @@ server.tool("createStory", "创建需求 - 支持创建软件需求(story)或用
|
|
|
246
112
|
content: [{ type: "text", text: JSON.stringify(story, null, 2) }]
|
|
247
113
|
};
|
|
248
114
|
});
|
|
249
|
-
server.tool("getPlanDetail", {
|
|
250
|
-
planId: z.number()
|
|
251
|
-
}, async ({ planId }) => {
|
|
252
|
-
if (!zentaoApi)
|
|
253
|
-
throw new Error("Please initialize Zentao API first");
|
|
254
|
-
const plan = await zentaoApi.getPlanDetail(planId);
|
|
255
|
-
return {
|
|
256
|
-
content: [{ type: "text", text: JSON.stringify(plan, null, 2) }]
|
|
257
|
-
};
|
|
258
|
-
});
|
|
259
|
-
server.tool("updatePlan", {
|
|
260
|
-
planId: z.number(),
|
|
261
|
-
update: z.object({
|
|
262
|
-
branch: z.number().optional(),
|
|
263
|
-
title: z.string().optional(),
|
|
264
|
-
begin: z.string().optional(),
|
|
265
|
-
end: z.string().optional(),
|
|
266
|
-
desc: z.string().optional()
|
|
267
|
-
})
|
|
268
|
-
}, async ({ planId, update }) => {
|
|
269
|
-
if (!zentaoApi)
|
|
270
|
-
throw new Error("Please initialize Zentao API first");
|
|
271
|
-
const plan = await zentaoApi.updatePlan(planId, update);
|
|
272
|
-
return {
|
|
273
|
-
content: [{ type: "text", text: JSON.stringify(plan, null, 2) }]
|
|
274
|
-
};
|
|
275
|
-
});
|
|
276
115
|
server.tool("deletePlan", {
|
|
277
116
|
planId: z.number()
|
|
278
117
|
}, async ({ planId }) => {
|
|
@@ -294,88 +133,61 @@ server.tool("linkStoriesToPlan", {
|
|
|
294
133
|
content: [{ type: "text", text: JSON.stringify(plan, null, 2) }]
|
|
295
134
|
};
|
|
296
135
|
});
|
|
297
|
-
server.tool("
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}, async ({ planId, storyIds }) => {
|
|
301
|
-
if (!zentaoApi)
|
|
302
|
-
throw new Error("Please initialize Zentao API first");
|
|
303
|
-
const plan = await zentaoApi.unlinkStoriesFromPlan(planId, storyIds);
|
|
304
|
-
return {
|
|
305
|
-
content: [{ type: "text", text: JSON.stringify(plan, null, 2) }]
|
|
306
|
-
};
|
|
307
|
-
});
|
|
308
|
-
server.tool("linkBugsToPlan", {
|
|
309
|
-
planId: z.number(),
|
|
310
|
-
bugIds: z.array(z.number())
|
|
311
|
-
}, async ({ planId, bugIds }) => {
|
|
136
|
+
server.tool("getStoryDetail", "获取需求详情 - 支持软件需求(story)和用户需求(requirement),自动识别类型", {
|
|
137
|
+
storyId: z.number().describe("需求ID(可以是story或requirement类型)")
|
|
138
|
+
}, async ({ storyId }) => {
|
|
312
139
|
if (!zentaoApi)
|
|
313
140
|
throw new Error("Please initialize Zentao API first");
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
141
|
+
const story = await zentaoApi.getStoryDetail(storyId, 'basic');
|
|
142
|
+
// 只返回基本信息
|
|
143
|
+
const result = {
|
|
144
|
+
id: story.id,
|
|
145
|
+
title: story.title,
|
|
146
|
+
status: story.status,
|
|
147
|
+
stage: story.stage,
|
|
148
|
+
pri: story.pri,
|
|
149
|
+
type: story.type,
|
|
150
|
+
category: story.category,
|
|
151
|
+
product: story.product,
|
|
152
|
+
productName: story.productName,
|
|
153
|
+
openedBy: story.openedBy,
|
|
154
|
+
openedDate: story.openedDate,
|
|
155
|
+
assignedTo: story.assignedTo
|
|
317
156
|
};
|
|
318
|
-
});
|
|
319
|
-
server.tool("unlinkBugsFromPlan", {
|
|
320
|
-
planId: z.number(),
|
|
321
|
-
bugIds: z.array(z.number())
|
|
322
|
-
}, async ({ planId, bugIds }) => {
|
|
323
|
-
if (!zentaoApi)
|
|
324
|
-
throw new Error("Please initialize Zentao API first");
|
|
325
|
-
const plan = await zentaoApi.unlinkBugsFromPlan(planId, bugIds);
|
|
326
157
|
return {
|
|
327
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
158
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
328
159
|
};
|
|
329
160
|
});
|
|
330
|
-
server.tool("
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
字段级别:
|
|
334
|
-
- basic: 基本信息(id, title, status, stage, pri等核心字段)
|
|
335
|
-
- detail: 详细信息(包含spec和verify,但不包含executions/tasks/stages/children等关联数据)
|
|
336
|
-
- full: 完整信息(包含所有字段,默认值)
|
|
337
|
-
`.trim())
|
|
338
|
-
}, async ({ storyId, fields }) => {
|
|
161
|
+
server.tool("getProjectExecutions", {
|
|
162
|
+
projectId: z.number()
|
|
163
|
+
}, async ({ projectId }) => {
|
|
339
164
|
if (!zentaoApi)
|
|
340
165
|
throw new Error("Please initialize Zentao API first");
|
|
341
|
-
const
|
|
342
|
-
//
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
else if (fields === 'detail') {
|
|
352
|
-
result.note = '显示详细信息但不包含关联数据(executions/tasks/stages/children),如需完整内容请使用 fields=full';
|
|
353
|
-
}
|
|
166
|
+
const executions = await zentaoApi.getProjectExecutions(projectId);
|
|
167
|
+
// 只返回基本字段
|
|
168
|
+
const simplified = executions.map((e) => ({
|
|
169
|
+
id: e.id,
|
|
170
|
+
name: e.name,
|
|
171
|
+
status: e.status,
|
|
172
|
+
begin: e.begin,
|
|
173
|
+
end: e.end,
|
|
174
|
+
progress: e.progress
|
|
175
|
+
}));
|
|
354
176
|
return {
|
|
355
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
177
|
+
content: [{ type: "text", text: JSON.stringify(simplified, null, 2) }]
|
|
356
178
|
};
|
|
357
179
|
});
|
|
358
|
-
server.tool("
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
begin: z.string().optional(),
|
|
363
|
-
end: z.string().optional(),
|
|
364
|
-
desc: z.string().optional(),
|
|
365
|
-
parent: z.number().optional()
|
|
366
|
-
}, async ({ productId, title, branch, begin, end, desc, parent }) => {
|
|
180
|
+
server.tool("getProjects", {
|
|
181
|
+
page: z.number().optional(),
|
|
182
|
+
limit: z.number().optional()
|
|
183
|
+
}, async ({ page, limit }) => {
|
|
367
184
|
if (!zentaoApi)
|
|
368
185
|
throw new Error("Please initialize Zentao API first");
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
begin,
|
|
373
|
-
end,
|
|
374
|
-
desc,
|
|
375
|
-
parent
|
|
376
|
-
});
|
|
186
|
+
const projects = await zentaoApi.getProjects(page, limit);
|
|
187
|
+
// 只返回 id 和 name 字段
|
|
188
|
+
const simplified = projects.map((p) => ({ id: p.id, name: p.name }));
|
|
377
189
|
return {
|
|
378
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
190
|
+
content: [{ type: "text", text: JSON.stringify(simplified, null, 2) }]
|
|
379
191
|
};
|
|
380
192
|
});
|
|
381
193
|
server.tool("changeStory", "需求变更 - 支持软件需求(story)和用户需求(requirement),自动识别类型", {
|
|
@@ -421,10 +233,10 @@ server.tool("changeStory", "需求变更 - 支持软件需求(story)和用户需
|
|
|
421
233
|
verify: z.string().optional(),
|
|
422
234
|
// 备注
|
|
423
235
|
comment: z.string().optional()
|
|
424
|
-
}).describe(`更新需求(使用 PUT 接口)
|
|
425
|
-
|
|
426
|
-
此工具使用标准的"修改需求其他字段"接口(PUT /stories/:id),支持修改29个字段。
|
|
427
|
-
自动处理评审人问题,无需手动设置 needNotReview。
|
|
236
|
+
}).describe(`更新需求(使用 PUT 接口)
|
|
237
|
+
|
|
238
|
+
此工具使用标准的"修改需求其他字段"接口(PUT /stories/:id),支持修改29个字段。
|
|
239
|
+
自动处理评审人问题,无需手动设置 needNotReview。
|
|
428
240
|
`.trim())
|
|
429
241
|
}, async ({ storyId, update }) => {
|
|
430
242
|
if (!zentaoApi)
|
|
@@ -434,94 +246,22 @@ server.tool("changeStory", "需求变更 - 支持软件需求(story)和用户需
|
|
|
434
246
|
content: [{ type: "text", text: JSON.stringify(story, null, 2) }]
|
|
435
247
|
};
|
|
436
248
|
});
|
|
437
|
-
server.tool("
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const result = await zentaoApi.getProductStories(productId, page, limit, fields);
|
|
446
|
-
// 返回分页信息和数据
|
|
447
|
-
const summary = {
|
|
448
|
-
summary: `当前第 ${result.page} 页,共 ${result.total} 条需求,本页显示 ${result.stories.length} 条`,
|
|
449
|
-
pagination: {
|
|
450
|
-
page: result.page,
|
|
451
|
-
limit: result.limit,
|
|
452
|
-
total: result.total,
|
|
453
|
-
totalPages: Math.ceil(result.total / result.limit)
|
|
454
|
-
},
|
|
455
|
-
stories: result.stories
|
|
456
|
-
};
|
|
457
|
-
return {
|
|
458
|
-
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
|
|
459
|
-
};
|
|
460
|
-
});
|
|
461
|
-
server.tool("getProgramDetail", { programId: z.number() }, async ({ programId }) => {
|
|
462
|
-
if (!zentaoApi)
|
|
463
|
-
throw new Error("Please initialize Zentao API first");
|
|
464
|
-
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.getProgramDetail(programId), null, 2) }] };
|
|
465
|
-
});
|
|
466
|
-
server.tool("createProgram", {
|
|
467
|
-
name: z.string().optional(), parent: z.string().optional(), PM: z.string().optional(),
|
|
468
|
-
budget: z.number().optional(), budgetUnit: z.string().optional(), desc: z.string().optional(),
|
|
469
|
-
begin: z.string().optional(), end: z.string().optional(), acl: z.string().optional(),
|
|
470
|
-
whitelist: z.array(z.string()).optional()
|
|
471
|
-
}, async (params) => {
|
|
472
|
-
if (!zentaoApi)
|
|
473
|
-
throw new Error("Please initialize Zentao API first");
|
|
474
|
-
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.createProgram(params), null, 2) }] };
|
|
475
|
-
});
|
|
476
|
-
server.tool("updateProgram", {
|
|
477
|
-
programId: z.number(),
|
|
478
|
-
update: z.object({
|
|
479
|
-
name: z.string().optional(), parent: z.string().optional(), PM: z.string().optional(),
|
|
480
|
-
budget: z.number().optional(), budgetUnit: z.string().optional(), desc: z.string().optional(),
|
|
481
|
-
begin: z.string().optional(), end: z.string().optional(), acl: z.string().optional(),
|
|
482
|
-
whitelist: z.array(z.string()).optional()
|
|
483
|
-
})
|
|
484
|
-
}, async ({ programId, update }) => {
|
|
485
|
-
if (!zentaoApi)
|
|
486
|
-
throw new Error("Please initialize Zentao API first");
|
|
487
|
-
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.updateProgram(programId, update), null, 2) }] };
|
|
488
|
-
});
|
|
489
|
-
server.tool("deleteProgram", { programId: z.number() }, async ({ programId }) => {
|
|
490
|
-
if (!zentaoApi)
|
|
491
|
-
throw new Error("Please initialize Zentao API first");
|
|
492
|
-
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.deleteProgram(programId), null, 2) }] };
|
|
493
|
-
});
|
|
494
|
-
server.tool("getProductDetail", { productId: z.number() }, async ({ productId }) => {
|
|
249
|
+
server.tool("createExecution", {
|
|
250
|
+
projectId: z.number(), project: z.number(), name: z.string(), code: z.string(),
|
|
251
|
+
begin: z.string(), end: z.string(), days: z.number().optional(), lifetime: z.string().optional(),
|
|
252
|
+
products: z.array(z.number()).describe("关联产品ID数组"),
|
|
253
|
+
PO: z.string().optional(), PM: z.string().optional(), QD: z.string().optional(),
|
|
254
|
+
RD: z.string().optional(), teamMembers: z.array(z.string()).optional(),
|
|
255
|
+
desc: z.string().optional(), acl: z.string().optional(), whitelist: z.array(z.string()).optional()
|
|
256
|
+
}, async ({ projectId, ...params }) => {
|
|
495
257
|
if (!zentaoApi)
|
|
496
258
|
throw new Error("Please initialize Zentao API first");
|
|
497
|
-
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.
|
|
259
|
+
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.createExecution(projectId, params), null, 2) }] };
|
|
498
260
|
});
|
|
499
|
-
server.tool("
|
|
500
|
-
name: z.string(), code: z.string(), program: z.number().optional(), line: z.number().optional(),
|
|
501
|
-
PO: z.string().optional(), QD: z.string().optional(), RD: z.string().optional(),
|
|
502
|
-
type: z.string().optional(), desc: z.string().optional(), acl: z.string().optional(),
|
|
503
|
-
whitelist: z.array(z.string()).optional()
|
|
504
|
-
}, async (params) => {
|
|
261
|
+
server.tool("deleteExecution", { executionId: z.number() }, async ({ executionId }) => {
|
|
505
262
|
if (!zentaoApi)
|
|
506
263
|
throw new Error("Please initialize Zentao API first");
|
|
507
|
-
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.
|
|
508
|
-
});
|
|
509
|
-
server.tool("updateProduct", {
|
|
510
|
-
productId: z.number(),
|
|
511
|
-
update: z.object({
|
|
512
|
-
name: z.string().optional(), code: z.string().optional(), type: z.string().optional(),
|
|
513
|
-
line: z.number().optional(), program: z.number().optional(), status: z.string().optional(),
|
|
514
|
-
desc: z.string().optional()
|
|
515
|
-
})
|
|
516
|
-
}, async ({ productId, update }) => {
|
|
517
|
-
if (!zentaoApi)
|
|
518
|
-
throw new Error("Please initialize Zentao API first");
|
|
519
|
-
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.updateProduct(productId, update), null, 2) }] };
|
|
520
|
-
});
|
|
521
|
-
server.tool("deleteProduct", { productId: z.number() }, async ({ productId }) => {
|
|
522
|
-
if (!zentaoApi)
|
|
523
|
-
throw new Error("Please initialize Zentao API first");
|
|
524
|
-
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.deleteProduct(productId), null, 2) }] };
|
|
264
|
+
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.deleteExecution(executionId), null, 2) }] };
|
|
525
265
|
});
|
|
526
266
|
server.tool("deleteStory", "删除需求 - 支持软件需求(story)和用户需求(requirement),自动识别类型", {
|
|
527
267
|
storyId: z.number().describe("需求ID(可以是story或requirement类型)")
|
|
@@ -530,387 +270,6 @@ server.tool("deleteStory", "删除需求 - 支持软件需求(story)和用户需
|
|
|
530
270
|
throw new Error("Please initialize Zentao API first");
|
|
531
271
|
return { content: [{ type: "text", text: JSON.stringify(await zentaoApi.deleteStory(storyId), null, 2) }] };
|
|
532
272
|
});
|
|
533
|
-
server.tool("getModules", {
|
|
534
|
-
type: z.enum(['story', 'task', 'bug', 'case', 'feedback', 'product']),
|
|
535
|
-
id: z.number(),
|
|
536
|
-
fields: z.string().optional()
|
|
537
|
-
}, async ({ type, id, fields }) => {
|
|
538
|
-
if (!zentaoApi)
|
|
539
|
-
throw new Error("Please initialize Zentao API first");
|
|
540
|
-
const response = await zentaoApi.getModules(type, id, fields);
|
|
541
|
-
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
542
|
-
});
|
|
543
|
-
server.tool("uploadFile", {
|
|
544
|
-
filePath: z.string().optional(),
|
|
545
|
-
base64Data: z.string().optional(),
|
|
546
|
-
filename: z.string().optional(),
|
|
547
|
-
uid: z.string().optional()
|
|
548
|
-
}, async ({ filePath, base64Data, filename, uid }) => {
|
|
549
|
-
if (!zentaoApi)
|
|
550
|
-
throw new Error("Please initialize Zentao API first");
|
|
551
|
-
const fs = await import('fs');
|
|
552
|
-
const path = await import('path');
|
|
553
|
-
let fileBuffer;
|
|
554
|
-
let finalFilename;
|
|
555
|
-
let savedPath;
|
|
556
|
-
// 如果提供了 base64 数据(复制的图片)
|
|
557
|
-
if (base64Data) {
|
|
558
|
-
// 创建 img 文件夹(如果不存在)
|
|
559
|
-
const imgDir = path.join(process.cwd(), 'img');
|
|
560
|
-
if (!fs.existsSync(imgDir)) {
|
|
561
|
-
fs.mkdirSync(imgDir, { recursive: true });
|
|
562
|
-
}
|
|
563
|
-
// 处理 base64 数据
|
|
564
|
-
const matches = base64Data.match(/^data:image\/(\w+);base64,(.+)$/);
|
|
565
|
-
if (matches) {
|
|
566
|
-
const ext = matches[1];
|
|
567
|
-
const data = matches[2];
|
|
568
|
-
fileBuffer = Buffer.from(data, 'base64');
|
|
569
|
-
finalFilename = filename || `image_${Date.now()}.${ext}`;
|
|
570
|
-
}
|
|
571
|
-
else {
|
|
572
|
-
// 直接的 base64 数据,没有 data URL 前缀
|
|
573
|
-
fileBuffer = Buffer.from(base64Data, 'base64');
|
|
574
|
-
finalFilename = filename || `image_${Date.now()}.png`;
|
|
575
|
-
}
|
|
576
|
-
// 保存到 img 文件夹
|
|
577
|
-
savedPath = path.join(imgDir, finalFilename);
|
|
578
|
-
fs.writeFileSync(savedPath, fileBuffer);
|
|
579
|
-
}
|
|
580
|
-
// 如果提供了文件路径
|
|
581
|
-
else if (filePath) {
|
|
582
|
-
fileBuffer = fs.readFileSync(filePath);
|
|
583
|
-
finalFilename = path.basename(filePath);
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
throw new Error("必须提供 filePath 或 base64Data 参数");
|
|
587
|
-
}
|
|
588
|
-
const result = await zentaoApi.uploadFile({
|
|
589
|
-
file: fileBuffer,
|
|
590
|
-
filename: finalFilename,
|
|
591
|
-
uid
|
|
592
|
-
});
|
|
593
|
-
// 生成禅道需要的 HTML 格式
|
|
594
|
-
const fileId = result.id;
|
|
595
|
-
const baseUrl = zentaoApi.getConfig().url;
|
|
596
|
-
const imageUrl = `${baseUrl}/zentao/entao/api.php?m=file&f=read&t=png&fileID=${fileId}`;
|
|
597
|
-
const imageHtml = `<p><img onload="setImageSize(this,0)" src="${imageUrl}" alt="${finalFilename}" /></p>`;
|
|
598
|
-
const response = {
|
|
599
|
-
upload: result,
|
|
600
|
-
fileId: fileId,
|
|
601
|
-
imageUrl: imageUrl,
|
|
602
|
-
imageHtml: imageHtml,
|
|
603
|
-
tip: `更新 Bug 描述时,请使用 imageHtml 字段中的 HTML 代码。图片会被包裹在 <p> 标签中以确保正确显示。`
|
|
604
|
-
};
|
|
605
|
-
if (savedPath) {
|
|
606
|
-
response.savedPath = savedPath;
|
|
607
|
-
}
|
|
608
|
-
return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
|
|
609
|
-
});
|
|
610
|
-
server.tool("uploadImageFromClipboard", "上传剪贴板图片到禅道 - 自动获取剪贴板中的图片并上传", {
|
|
611
|
-
filename: z.string().optional().describe("自定义文件名,默认为 clipboard_时间戳.png"),
|
|
612
|
-
uid: z.string().optional().describe("禅道文件UID,用于关联需求或Bug")
|
|
613
|
-
}, async ({ filename, uid }) => {
|
|
614
|
-
if (!zentaoApi) {
|
|
615
|
-
return {
|
|
616
|
-
content: [{
|
|
617
|
-
type: "text",
|
|
618
|
-
text: JSON.stringify({ error: "请先初始化禅道API配置" }, null, 2)
|
|
619
|
-
}],
|
|
620
|
-
isError: true
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
const fs = await import('fs');
|
|
624
|
-
const path = await import('path');
|
|
625
|
-
const { execSync } = await import('child_process');
|
|
626
|
-
try {
|
|
627
|
-
process.stdout.write('[uploadImageFromClipboard] 开始执行...\n');
|
|
628
|
-
// 读取系统剪贴板图片
|
|
629
|
-
let fileBuffer;
|
|
630
|
-
let finalFilename;
|
|
631
|
-
// Windows: 使用 PowerShell 脚本读取剪贴板
|
|
632
|
-
if (process.platform === 'win32') {
|
|
633
|
-
process.stdout.write('[uploadImageFromClipboard] Windows平台,使用PowerShell脚本读取剪贴板...\n');
|
|
634
|
-
// 内嵌 PowerShell 脚本,避免依赖外部文件
|
|
635
|
-
const psScript = `Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing; $img = [System.Windows.Forms.Clipboard]::GetImage(); if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); $bytes = $ms.ToArray(); $ms.Close(); $base64 = [Convert]::ToBase64String($bytes); Write-Output $base64 } else { Write-Output 'NoImage' }`;
|
|
636
|
-
try {
|
|
637
|
-
// 使用 Base64 编码 PowerShell 脚本以避免转义问题
|
|
638
|
-
const psScriptBase64 = Buffer.from(psScript, 'utf16le').toString('base64');
|
|
639
|
-
const result = execSync(`powershell -ExecutionPolicy Bypass -EncodedCommand ${psScriptBase64}`, {
|
|
640
|
-
encoding: 'utf8',
|
|
641
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
642
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
643
|
-
}).trim();
|
|
644
|
-
if (!result || result.includes('NoImage') || result.includes('Error')) {
|
|
645
|
-
process.stderr.write('[uploadImageFromClipboard] 剪贴板中没有图片\n');
|
|
646
|
-
return {
|
|
647
|
-
content: [{
|
|
648
|
-
type: "text",
|
|
649
|
-
text: JSON.stringify({
|
|
650
|
-
error: "剪贴板中没有图片",
|
|
651
|
-
tip: "请先复制图片,不要粘贴到输入框,直接告诉我'上传'"
|
|
652
|
-
}, null, 2)
|
|
653
|
-
}],
|
|
654
|
-
isError: true
|
|
655
|
-
};
|
|
656
|
-
}
|
|
657
|
-
fileBuffer = Buffer.from(result, 'base64');
|
|
658
|
-
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
659
|
-
process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
|
|
660
|
-
}
|
|
661
|
-
catch (err) {
|
|
662
|
-
process.stderr.write('[uploadImageFromClipboard] Windows读取剪贴板失败: ' + String(err) + '\n');
|
|
663
|
-
return {
|
|
664
|
-
content: [{
|
|
665
|
-
type: "text",
|
|
666
|
-
text: JSON.stringify({
|
|
667
|
-
error: "读取剪贴板失败",
|
|
668
|
-
details: err.message,
|
|
669
|
-
tip: "请确保已复制图片到剪贴板"
|
|
670
|
-
}, null, 2)
|
|
671
|
-
}],
|
|
672
|
-
isError: true
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
// macOS: 使用 pngpaste
|
|
677
|
-
else if (process.platform === 'darwin') {
|
|
678
|
-
process.stdout.write('[uploadImageFromClipboard] macOS平台,使用pngpaste读取剪贴板...\n');
|
|
679
|
-
const tempFile = path.join(process.cwd(), '.temp_clipboard.png');
|
|
680
|
-
try {
|
|
681
|
-
execSync(`pngpaste "${tempFile}"`);
|
|
682
|
-
fileBuffer = fs.readFileSync(tempFile);
|
|
683
|
-
fs.unlinkSync(tempFile);
|
|
684
|
-
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
685
|
-
process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
|
|
686
|
-
}
|
|
687
|
-
catch (err) {
|
|
688
|
-
process.stderr.write('[uploadImageFromClipboard] macOS读取剪贴板失败: ' + String(err) + '\n');
|
|
689
|
-
return {
|
|
690
|
-
content: [{
|
|
691
|
-
type: "text",
|
|
692
|
-
text: JSON.stringify({
|
|
693
|
-
error: "剪贴板中没有图片或pngpaste未安装",
|
|
694
|
-
tip: "请先安装 pngpaste: brew install pngpaste"
|
|
695
|
-
}, null, 2)
|
|
696
|
-
}],
|
|
697
|
-
isError: true
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
// Linux: 使用 xclip
|
|
702
|
-
else {
|
|
703
|
-
process.stdout.write('[uploadImageFromClipboard] Linux平台,使用xclip读取剪贴板...\n');
|
|
704
|
-
try {
|
|
705
|
-
const buffer = execSync('xclip -selection clipboard -t image/png -o', {
|
|
706
|
-
maxBuffer: 10 * 1024 * 1024
|
|
707
|
-
});
|
|
708
|
-
fileBuffer = buffer;
|
|
709
|
-
finalFilename = filename || `clipboard_${Date.now()}.png`;
|
|
710
|
-
process.stdout.write(`[uploadImageFromClipboard] 已读取剪贴板图片,大小: ${fileBuffer.length} bytes\n`);
|
|
711
|
-
}
|
|
712
|
-
catch (err) {
|
|
713
|
-
process.stderr.write('[uploadImageFromClipboard] Linux读取剪贴板失败: ' + String(err) + '\n');
|
|
714
|
-
return {
|
|
715
|
-
content: [{
|
|
716
|
-
type: "text",
|
|
717
|
-
text: JSON.stringify({
|
|
718
|
-
error: "剪贴板中没有图片或xclip未安装",
|
|
719
|
-
tip: "请先安装 xclip: sudo apt-get install xclip"
|
|
720
|
-
}, null, 2)
|
|
721
|
-
}],
|
|
722
|
-
isError: true
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
// 创建 img 文件夹
|
|
727
|
-
const imgDir = path.join(process.cwd(), 'img');
|
|
728
|
-
if (!fs.existsSync(imgDir)) {
|
|
729
|
-
process.stdout.write(`[uploadImageFromClipboard] 创建目录: ${imgDir}\n`);
|
|
730
|
-
fs.mkdirSync(imgDir, { recursive: true });
|
|
731
|
-
}
|
|
732
|
-
// 保存到 img 文件夹
|
|
733
|
-
const savedPath = path.join(imgDir, finalFilename);
|
|
734
|
-
fs.writeFileSync(savedPath, fileBuffer);
|
|
735
|
-
process.stdout.write(`[uploadImageFromClipboard] 图片已保存到: ${savedPath}\n`);
|
|
736
|
-
// 上传到禅道
|
|
737
|
-
process.stdout.write('[uploadImageFromClipboard] 开始上传到禅道...\n');
|
|
738
|
-
const uploadResult = await zentaoApi.uploadFile({
|
|
739
|
-
file: fileBuffer,
|
|
740
|
-
filename: finalFilename,
|
|
741
|
-
uid
|
|
742
|
-
});
|
|
743
|
-
process.stdout.write('[uploadImageFromClipboard] 上传成功,结果: ' + JSON.stringify(uploadResult) + '\n');
|
|
744
|
-
// 生成禅道需要的 HTML 格式
|
|
745
|
-
const fileId = uploadResult.id;
|
|
746
|
-
const baseUrl = zentaoApi.getConfig().url;
|
|
747
|
-
const imageUrl = `${baseUrl}/zentao/file-read-${fileId}.png`;
|
|
748
|
-
const imageHtml = `<p><img onload="setImageSize(this,0)" src="${imageUrl}" alt="${finalFilename}" /></p>`;
|
|
749
|
-
const response = {
|
|
750
|
-
success: true,
|
|
751
|
-
upload: uploadResult,
|
|
752
|
-
savedPath: savedPath,
|
|
753
|
-
filename: finalFilename,
|
|
754
|
-
fileSize: fileBuffer.length,
|
|
755
|
-
fileId: fileId,
|
|
756
|
-
imageUrl: imageUrl,
|
|
757
|
-
imageHtml: imageHtml,
|
|
758
|
-
message: `图片已保存到本地并上传到禅道`,
|
|
759
|
-
tip: `更新 Bug 描述时,请使用 imageHtml 字段中的 HTML 代码。图片会被包裹在 <p> 标签中以确保正确显示。`
|
|
760
|
-
};
|
|
761
|
-
process.stdout.write('[uploadImageFromClipboard] 返回结果: ' + JSON.stringify(response) + '\n');
|
|
762
|
-
return {
|
|
763
|
-
content: [{
|
|
764
|
-
type: "text",
|
|
765
|
-
text: JSON.stringify(response, null, 2)
|
|
766
|
-
}]
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
catch (error) {
|
|
770
|
-
process.stderr.write('[uploadImageFromClipboard] 发生错误: ' + String(error) + '\n');
|
|
771
|
-
const errorResponse = {
|
|
772
|
-
success: false,
|
|
773
|
-
error: error.message || String(error),
|
|
774
|
-
stack: error.stack,
|
|
775
|
-
tip: "请检查:\n1. 确保已复制图片到剪贴板\n2. 不要粘贴到输入框,直接说'上传剪贴板图片'\n3. 或使用命令行: upload.bat"
|
|
776
|
-
};
|
|
777
|
-
return {
|
|
778
|
-
content: [{
|
|
779
|
-
type: "text",
|
|
780
|
-
text: JSON.stringify(errorResponse, null, 2)
|
|
781
|
-
}],
|
|
782
|
-
isError: true
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
});
|
|
786
|
-
server.tool("downloadFile", {
|
|
787
|
-
fileId: z.number(),
|
|
788
|
-
savePath: z.string()
|
|
789
|
-
}, async ({ fileId, savePath }) => {
|
|
790
|
-
if (!zentaoApi)
|
|
791
|
-
throw new Error("Please initialize Zentao API first");
|
|
792
|
-
const fs = await import('fs');
|
|
793
|
-
const fileBuffer = await zentaoApi.downloadFile(fileId);
|
|
794
|
-
fs.writeFileSync(savePath, fileBuffer);
|
|
795
|
-
return { content: [{ type: "text", text: `文件已下载到: ${savePath}` }] };
|
|
796
|
-
});
|
|
797
|
-
server.tool("getComments", "获取评论列表 - 获取指定对象的所有评论", {
|
|
798
|
-
objectType: z.enum([
|
|
799
|
-
'story', // 软件需求
|
|
800
|
-
'requirement', // 用户需求
|
|
801
|
-
'task', // 任务
|
|
802
|
-
'bug', // Bug
|
|
803
|
-
'testcase', // 测试用例
|
|
804
|
-
'testtask', // 测试单
|
|
805
|
-
'todo', // 待办
|
|
806
|
-
'doc', // 文档
|
|
807
|
-
'execution', // 执行/迭代
|
|
808
|
-
'project', // 项目
|
|
809
|
-
'doctemplate' // 文档模板
|
|
810
|
-
]).describe("对象类型"),
|
|
811
|
-
objectID: z.number().describe("对象ID")
|
|
812
|
-
}, async ({ objectType, objectID }) => {
|
|
813
|
-
if (!zentaoApi)
|
|
814
|
-
throw new Error("Please initialize Zentao API first");
|
|
815
|
-
const comments = await zentaoApi.getComments(objectType, objectID);
|
|
816
|
-
return {
|
|
817
|
-
content: [{
|
|
818
|
-
type: "text",
|
|
819
|
-
text: JSON.stringify({
|
|
820
|
-
total: comments.length,
|
|
821
|
-
objectType,
|
|
822
|
-
objectID,
|
|
823
|
-
comments
|
|
824
|
-
}, null, 2)
|
|
825
|
-
}]
|
|
826
|
-
};
|
|
827
|
-
});
|
|
828
|
-
server.tool("getCommentDetail", "获取单条评论详情", {
|
|
829
|
-
commentId: z.number().describe("评论ID")
|
|
830
|
-
}, async ({ commentId }) => {
|
|
831
|
-
if (!zentaoApi)
|
|
832
|
-
throw new Error("Please initialize Zentao API first");
|
|
833
|
-
const comment = await zentaoApi.getCommentDetail(commentId);
|
|
834
|
-
return {
|
|
835
|
-
content: [{
|
|
836
|
-
type: "text",
|
|
837
|
-
text: JSON.stringify(comment, null, 2)
|
|
838
|
-
}]
|
|
839
|
-
};
|
|
840
|
-
});
|
|
841
|
-
server.tool("addComment", "添加评论 - 为需求、任务、Bug等对象添加评论", {
|
|
842
|
-
objectType: z.enum([
|
|
843
|
-
'story', // 软件需求
|
|
844
|
-
'requirement', // 用户需求
|
|
845
|
-
'task', // 任务
|
|
846
|
-
'bug', // Bug
|
|
847
|
-
'testcase', // 测试用例
|
|
848
|
-
'testtask', // 测试单
|
|
849
|
-
'todo', // 待办
|
|
850
|
-
'doc', // 文档
|
|
851
|
-
'execution', // 执行/迭代
|
|
852
|
-
'project', // 项目
|
|
853
|
-
'doctemplate' // 文档模板
|
|
854
|
-
]).describe("对象类型"),
|
|
855
|
-
objectID: z.number().describe("对象ID"),
|
|
856
|
-
comment: z.string().describe("评论内容,支持HTML格式"),
|
|
857
|
-
uid: z.string().optional().describe("唯一标识符(可选)")
|
|
858
|
-
}, async ({ objectType, objectID, comment, uid }) => {
|
|
859
|
-
if (!zentaoApi)
|
|
860
|
-
throw new Error("Please initialize Zentao API first");
|
|
861
|
-
const result = await zentaoApi.addComment({
|
|
862
|
-
objectType,
|
|
863
|
-
objectID,
|
|
864
|
-
comment,
|
|
865
|
-
uid
|
|
866
|
-
});
|
|
867
|
-
return {
|
|
868
|
-
content: [{
|
|
869
|
-
type: "text",
|
|
870
|
-
text: JSON.stringify({
|
|
871
|
-
message: '评论添加成功',
|
|
872
|
-
comment: result
|
|
873
|
-
}, null, 2)
|
|
874
|
-
}]
|
|
875
|
-
};
|
|
876
|
-
});
|
|
877
|
-
server.tool("updateComment", "更新评论 - 只能更新自己的评论", {
|
|
878
|
-
commentId: z.number().describe("评论ID"),
|
|
879
|
-
comment: z.string().describe("更新后的评论内容,支持HTML格式"),
|
|
880
|
-
uid: z.string().optional().describe("唯一标识符(可选)")
|
|
881
|
-
}, async ({ commentId, comment, uid }) => {
|
|
882
|
-
if (!zentaoApi)
|
|
883
|
-
throw new Error("Please initialize Zentao API first");
|
|
884
|
-
const result = await zentaoApi.updateComment(commentId, {
|
|
885
|
-
comment,
|
|
886
|
-
uid
|
|
887
|
-
});
|
|
888
|
-
return {
|
|
889
|
-
content: [{
|
|
890
|
-
type: "text",
|
|
891
|
-
text: JSON.stringify({
|
|
892
|
-
message: '评论更新成功',
|
|
893
|
-
comment: result
|
|
894
|
-
}, null, 2)
|
|
895
|
-
}]
|
|
896
|
-
};
|
|
897
|
-
});
|
|
898
|
-
server.tool("deleteComment", "删除评论 - 只能删除自己的评论,管理员可以删除所有评论", {
|
|
899
|
-
commentId: z.number().describe("评论ID")
|
|
900
|
-
}, async ({ commentId }) => {
|
|
901
|
-
if (!zentaoApi)
|
|
902
|
-
throw new Error("Please initialize Zentao API first");
|
|
903
|
-
const result = await zentaoApi.deleteComment(commentId);
|
|
904
|
-
return {
|
|
905
|
-
content: [{
|
|
906
|
-
type: "text",
|
|
907
|
-
text: JSON.stringify({
|
|
908
|
-
status: 'success',
|
|
909
|
-
message: result.message || '评论删除成功'
|
|
910
|
-
}, null, 2)
|
|
911
|
-
}]
|
|
912
|
-
};
|
|
913
|
-
});
|
|
914
273
|
await startServerTransport(server, { args }).catch(err => {
|
|
915
274
|
process.stderr.write('[FATAL] MCP server failed: ' + String(err) + '\n');
|
|
916
275
|
process.exit(1);
|