@zzp123/mcp-zentao 1.8.0 → 1.8.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.
@@ -9,8 +9,19 @@ export declare class ZentaoAPI {
9
9
  getMyTasks(status?: TaskStatus): Promise<Task[]>;
10
10
  getTaskDetail(taskId: number): Promise<Task>;
11
11
  getProducts(): Promise<Product[]>;
12
- getMyBugs(status?: BugStatus, productId?: number): Promise<Bug[]>;
13
- getBugDetail(bugId: number): Promise<Bug>;
12
+ getMyBugs(status?: BugStatus, productId?: number, page?: number, limit?: number): Promise<{
13
+ page: number;
14
+ total: number;
15
+ limit: number;
16
+ bugs: Bug[];
17
+ }>;
18
+ getProductBugs(productId: number, page?: number, limit?: number, status?: BugStatus): Promise<{
19
+ page: number;
20
+ total: number;
21
+ limit: number;
22
+ bugs: Bug[];
23
+ }>;
24
+ getBugDetail(bugId: number, fields?: 'basic' | 'detail' | 'full'): Promise<any>;
14
25
  resolveBug(bugId: number, resolution: ResolveBugRequest): Promise<Bug>;
15
26
  updateTask(taskId: number, update: TaskUpdate): Promise<Task>;
16
27
  finishTask(taskId: number, update?: TaskUpdate): Promise<Task>;
@@ -28,16 +39,31 @@ export declare class ZentaoAPI {
28
39
  linkBugsToPlan(planId: number, bugIds: number[]): Promise<Plan>;
29
40
  unlinkBugsFromPlan(planId: number, bugIds: number[]): Promise<Plan>;
30
41
  getProjectReleases(projectId: number): Promise<Release[]>;
31
- getStoryDetail(storyId: number): Promise<Story>;
42
+ getStoryDetail(storyId: number, fields?: 'basic' | 'detail' | 'full'): Promise<any>;
32
43
  createBug(productId: number, bug: CreateBugRequest): Promise<Bug>;
33
44
  getProjectExecutions(projectId: number): Promise<Execution[]>;
34
45
  createPlan(productId: number, plan: CreatePlanRequest): Promise<Plan>;
35
46
  getProjects(page?: number, limit?: number): Promise<Project[]>;
47
+ /**
48
+ * 修改需求(使用最新的 PUT /stories/:id 接口)
49
+ *
50
+ * 注意:此方法使用"修改需求其他字段"接口(PUT),支持修改29个字段
51
+ * 已废弃:旧的"变更需求"接口(POST /stories/:id/change)仅支持3个字段
52
+ *
53
+ * @param storyId 需求ID
54
+ * @param update 需要更新的字段
55
+ * @returns 更新后的需求信息
56
+ */
36
57
  changeStory(storyId: number, update: ChangeStoryRequest): Promise<Story>;
37
58
  deleteTask(taskId: number): Promise<{
38
59
  message: string;
39
60
  }>;
40
- getProductStories(productId: number): Promise<Story[]>;
61
+ getProductStories(productId: number, page?: number, limit?: number): Promise<{
62
+ page: number;
63
+ total: number;
64
+ limit: number;
65
+ stories: Story[];
66
+ }>;
41
67
  getProgramDetail(programId: number): Promise<Program>;
42
68
  createProgram(program: CreateProgramRequest): Promise<Program>;
43
69
  updateProgram(programId: number, update: UpdateProgramRequest): Promise<Program>;
@@ -111,7 +111,7 @@ export class ZentaoAPI {
111
111
  throw error;
112
112
  }
113
113
  }
114
- async getMyBugs(status, productId) {
114
+ async getMyBugs(status, productId, page, limit) {
115
115
  if (!productId) {
116
116
  // 如果没有提供产品ID,获取第一个可用的产品
117
117
  const products = await this.getProducts();
@@ -121,20 +121,36 @@ export class ZentaoAPI {
121
121
  productId = products[0].id;
122
122
  console.log(`使用第一个可用的产品ID: ${productId}`);
123
123
  }
124
+ // 默认每页20条,最多100条
125
+ const finalLimit = limit ? Math.min(limit, 100) : 20;
126
+ const finalPage = page || 1;
124
127
  const params = {
125
128
  assignedTo: this.config.username,
126
129
  status: status || 'all',
127
- product: productId
130
+ product: productId,
131
+ page: finalPage,
132
+ limit: finalLimit
128
133
  };
129
134
  try {
130
- console.log('正在获取Bug列表,参数:', params);
135
+ console.log(`正在获取Bug列表 (page=${finalPage}, limit=${finalLimit}),参数:`, params);
131
136
  const response = await this.request('GET', '/bugs', params);
132
- console.log('Bug列表响应:', response);
137
+ console.log(`Bug列表响应: 获取到 ${response.bugs?.length || 0} 条数据`);
133
138
  if (Array.isArray(response)) {
134
- return response;
139
+ // 如果返回的是数组,转换为标准格式
140
+ return {
141
+ page: finalPage,
142
+ total: response.length,
143
+ limit: finalLimit,
144
+ bugs: response
145
+ };
135
146
  }
136
147
  else if (response && typeof response === 'object' && Array.isArray(response.bugs)) {
137
- return response.bugs;
148
+ return {
149
+ page: response.page || finalPage,
150
+ total: response.total || response.bugs.length,
151
+ limit: response.limit || finalLimit,
152
+ bugs: response.bugs
153
+ };
138
154
  }
139
155
  throw new Error(`获取Bug列表失败: 响应格式不正确 ${JSON.stringify(response)}`);
140
156
  }
@@ -146,12 +162,80 @@ export class ZentaoAPI {
146
162
  throw error;
147
163
  }
148
164
  }
149
- async getBugDetail(bugId) {
165
+ async getProductBugs(productId, page, limit, status) {
166
+ try {
167
+ // 默认每页20条,最多100条
168
+ const finalLimit = limit ? Math.min(limit, 100) : 20;
169
+ const finalPage = page || 1;
170
+ console.log(`正在获取产品 ${productId} 的Bug列表 (page=${finalPage}, limit=${finalLimit})...`);
171
+ // 构建查询参数
172
+ const queryParams = new URLSearchParams();
173
+ queryParams.append('page', finalPage.toString());
174
+ queryParams.append('limit', finalLimit.toString());
175
+ if (status && status !== 'all') {
176
+ queryParams.append('status', status);
177
+ }
178
+ const response = await this.request('GET', `/products/${productId}/bugs?${queryParams.toString()}`);
179
+ console.log(`产品Bug列表响应: 获取到 ${response.bugs?.length || 0} 条数据`);
180
+ if (Array.isArray(response)) {
181
+ // 如果返回的是数组,转换为标准格式
182
+ return {
183
+ page: finalPage,
184
+ total: response.length,
185
+ limit: finalLimit,
186
+ bugs: response
187
+ };
188
+ }
189
+ else if (response && typeof response === 'object') {
190
+ if (Array.isArray(response.bugs)) {
191
+ return {
192
+ page: response.page || finalPage,
193
+ total: response.total || response.bugs.length,
194
+ limit: response.limit || finalLimit,
195
+ bugs: response.bugs
196
+ };
197
+ }
198
+ }
199
+ throw new Error(`获取产品Bug列表失败: 响应格式不正确 ${JSON.stringify(response)}`);
200
+ }
201
+ catch (error) {
202
+ console.error('获取产品Bug列表失败:', error);
203
+ throw error;
204
+ }
205
+ }
206
+ async getBugDetail(bugId, fields) {
150
207
  try {
151
- console.log(`正在获取Bug ${bugId} 的详情...`);
208
+ console.log(`正在获取Bug ${bugId} 的详情 (fields=${fields || 'full'})...`);
152
209
  const response = await this.request('GET', `/bugs/${bugId}`);
153
210
  console.log('Bug详情响应:', response);
154
- return response;
211
+ // 根据 fields 参数过滤返回的字段
212
+ const fieldLevel = fields || 'full';
213
+ if (fieldLevel === 'basic') {
214
+ // 基本信息:只返回核心字段
215
+ return {
216
+ id: response.id,
217
+ title: response.title,
218
+ product: response.product,
219
+ module: response.module,
220
+ status: response.status,
221
+ severity: response.severity,
222
+ pri: response.pri,
223
+ type: response.type,
224
+ assignedTo: response.assignedTo,
225
+ openedBy: response.openedBy,
226
+ openedDate: response.openedDate,
227
+ deadline: response.deadline
228
+ };
229
+ }
230
+ else if (fieldLevel === 'detail') {
231
+ // 详细信息:包含步骤描述,但排除不常用字段
232
+ const { files, cases, linkBugs, ...detailData } = response;
233
+ return detailData;
234
+ }
235
+ else {
236
+ // 完整信息:返回所有字段
237
+ return response;
238
+ }
155
239
  }
156
240
  catch (error) {
157
241
  console.error('获取Bug详情失败:', error);
@@ -414,12 +498,39 @@ export class ZentaoAPI {
414
498
  throw error;
415
499
  }
416
500
  }
417
- async getStoryDetail(storyId) {
501
+ async getStoryDetail(storyId, fields) {
418
502
  try {
419
- console.log(`正在获取需求 ${storyId} 的详情...`);
503
+ console.log(`正在获取需求 ${storyId} 的详情 (fields=${fields || 'full'})...`);
420
504
  const response = await this.request('GET', `/stories/${storyId}`);
421
505
  console.log('需求详情响应:', response);
422
- return response;
506
+ // 根据 fields 参数过滤返回的字段
507
+ const fieldLevel = fields || 'full';
508
+ if (fieldLevel === 'basic') {
509
+ // 基本信息:只返回核心字段
510
+ return {
511
+ id: response.id,
512
+ title: response.title,
513
+ product: response.product,
514
+ module: response.module,
515
+ status: response.status,
516
+ stage: response.stage,
517
+ pri: response.pri,
518
+ category: response.category,
519
+ estimate: response.estimate,
520
+ assignedTo: response.assignedTo,
521
+ openedBy: response.openedBy,
522
+ openedDate: response.openedDate
523
+ };
524
+ }
525
+ else if (fieldLevel === 'detail') {
526
+ // 详细信息:包含描述和验收标准,但不包含关联数据
527
+ const { executions, tasks, stages, children, ...detailData } = response;
528
+ return detailData;
529
+ }
530
+ else {
531
+ // 完整信息:返回所有字段
532
+ return response;
533
+ }
423
534
  }
424
535
  catch (error) {
425
536
  console.error('获取需求详情失败:', error);
@@ -495,9 +606,19 @@ export class ZentaoAPI {
495
606
  throw error;
496
607
  }
497
608
  }
609
+ /**
610
+ * 修改需求(使用最新的 PUT /stories/:id 接口)
611
+ *
612
+ * 注意:此方法使用"修改需求其他字段"接口(PUT),支持修改29个字段
613
+ * 已废弃:旧的"变更需求"接口(POST /stories/:id/change)仅支持3个字段
614
+ *
615
+ * @param storyId 需求ID
616
+ * @param update 需要更新的字段
617
+ * @returns 更新后的需求信息
618
+ */
498
619
  async changeStory(storyId, update) {
499
620
  try {
500
- console.log(`正在变更需求 ${storyId}...`);
621
+ console.log(`正在修改需求 ${storyId} (使用 PUT 接口)...`);
501
622
  // 自动处理评审人问题
502
623
  const updateData = { ...update };
503
624
  // 如果用户没有设置评审人,也没有指定无需评审,则自动跳过评审
@@ -505,12 +626,13 @@ export class ZentaoAPI {
505
626
  updateData.needNotReview = true;
506
627
  console.log('自动设置needNotReview=true以跳过评审要求');
507
628
  }
629
+ // 使用 PUT /stories/:id 接口(功能完整的"修改需求其他字段"接口)
508
630
  const response = await this.request('PUT', `/stories/${storyId}`, undefined, updateData);
509
- console.log('变更需求响应:', response);
631
+ console.log('修改需求响应:', response);
510
632
  return response;
511
633
  }
512
634
  catch (error) {
513
- console.error('变更需求失败:', error);
635
+ console.error('修改需求失败:', error);
514
636
  throw error;
515
637
  }
516
638
  }
@@ -526,17 +648,35 @@ export class ZentaoAPI {
526
648
  throw error;
527
649
  }
528
650
  }
529
- async getProductStories(productId) {
651
+ async getProductStories(productId, page, limit) {
530
652
  try {
531
- console.log(`正在获取产品 ${productId} 的需求列表...`);
532
- const response = await this.request('GET', `/products/${productId}/stories`);
533
- console.log('产品需求列表响应:', response);
653
+ // 默认每页20条,最多100条
654
+ const finalLimit = limit ? Math.min(limit, 100) : 20;
655
+ const finalPage = page || 1;
656
+ console.log(`正在获取产品 ${productId} 的需求列表 (page=${finalPage}, limit=${finalLimit})...`);
657
+ // 构建查询参数
658
+ const queryParams = new URLSearchParams();
659
+ queryParams.append('page', finalPage.toString());
660
+ queryParams.append('limit', finalLimit.toString());
661
+ const response = await this.request('GET', `/products/${productId}/stories?${queryParams.toString()}`);
662
+ console.log(`产品需求列表响应: 获取到 ${response.stories?.length || 0} 条数据`);
534
663
  if (Array.isArray(response)) {
535
- return response;
664
+ // 如果返回的是数组,转换为标准格式
665
+ return {
666
+ page: finalPage,
667
+ total: response.length,
668
+ limit: finalLimit,
669
+ stories: response
670
+ };
536
671
  }
537
672
  else if (response && typeof response === 'object') {
538
673
  if (Array.isArray(response.stories)) {
539
- return response.stories;
674
+ return {
675
+ page: response.page || finalPage,
676
+ total: response.total || response.stories.length,
677
+ limit: response.limit || finalLimit,
678
+ stories: response.stories
679
+ };
540
680
  }
541
681
  }
542
682
  throw new Error(`获取产品需求列表失败: 响应格式不正确 ${JSON.stringify(response)}`);
package/dist/index.js CHANGED
@@ -89,24 +89,80 @@ server.tool("getProducts", {}, async () => {
89
89
  // Add getMyBugs tool
90
90
  server.tool("getMyBugs", {
91
91
  status: z.enum(['active', 'resolved', 'closed', 'all']).optional(),
92
- productId: z.number().optional()
93
- }, async ({ status, productId }) => {
94
- if (!zentaoApi)
95
- throw new Error("Please initialize Zentao API first");
96
- const bugs = await zentaoApi.getMyBugs(status, productId);
92
+ productId: z.number().optional(),
93
+ page: z.number().optional().describe("页码,从1开始,默认为1"),
94
+ limit: z.number().optional().describe("每页数量,默认20,最大100")
95
+ }, async ({ status, productId, page, limit }) => {
96
+ if (!zentaoApi)
97
+ throw new Error("Please initialize Zentao API first");
98
+ const result = await zentaoApi.getMyBugs(status, productId, page, limit);
99
+ // 返回分页信息和数据
100
+ const summary = {
101
+ summary: `当前第 ${result.page} 页,共 ${result.total} 个Bug,本页显示 ${result.bugs.length} 个`,
102
+ pagination: {
103
+ page: result.page,
104
+ limit: result.limit,
105
+ total: result.total,
106
+ totalPages: Math.ceil(result.total / result.limit)
107
+ },
108
+ bugs: result.bugs
109
+ };
110
+ return {
111
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
112
+ };
113
+ });
114
+ // Add getProductBugs tool
115
+ server.tool("getProductBugs", {
116
+ productId: z.number(),
117
+ page: z.number().optional().describe("页码,从1开始,默认为1"),
118
+ limit: z.number().optional().describe("每页数量,默认20,最大100"),
119
+ status: z.enum(['active', 'resolved', 'closed', 'all']).optional()
120
+ }, async ({ productId, page, limit, status }) => {
121
+ if (!zentaoApi)
122
+ throw new Error("Please initialize Zentao API first");
123
+ const result = await zentaoApi.getProductBugs(productId, page, limit, status);
124
+ // 返回分页信息和数据
125
+ const summary = {
126
+ summary: `当前第 ${result.page} 页,共 ${result.total} 个Bug,本页显示 ${result.bugs.length} 个`,
127
+ pagination: {
128
+ page: result.page,
129
+ limit: result.limit,
130
+ total: result.total,
131
+ totalPages: Math.ceil(result.total / result.limit)
132
+ },
133
+ bugs: result.bugs
134
+ };
97
135
  return {
98
- content: [{ type: "text", text: JSON.stringify(bugs, null, 2) }]
136
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
99
137
  };
100
138
  });
101
139
  // Add getBugDetail tool
102
140
  server.tool("getBugDetail", {
103
- bugId: z.number()
104
- }, async ({ bugId }) => {
105
- if (!zentaoApi)
106
- throw new Error("Please initialize Zentao API first");
107
- const bug = await zentaoApi.getBugDetail(bugId);
141
+ bugId: z.number(),
142
+ fields: z.enum(['basic', 'detail', 'full']).optional().describe(`
143
+ 字段级别:
144
+ - basic: 基本信息(id, title, status, severity, pri等核心字段)
145
+ - detail: 详细信息(包含steps步骤,但不包含files/cases/linkBugs等关联数据)
146
+ - full: 完整信息(包含所有字段,默认值)
147
+ `.trim())
148
+ }, async ({ bugId, fields }) => {
149
+ if (!zentaoApi)
150
+ throw new Error("Please initialize Zentao API first");
151
+ const bug = await zentaoApi.getBugDetail(bugId, fields);
152
+ // 添加说明信息
153
+ const result = {
154
+ fieldLevel: fields || 'full',
155
+ bug: bug
156
+ };
157
+ // 根据字段级别添加提示
158
+ if (fields === 'basic') {
159
+ result.note = '仅显示基本信息,如需完整内容请使用 fields=detail 或 fields=full';
160
+ }
161
+ else if (fields === 'detail') {
162
+ result.note = '显示详细信息但不包含关联数据(files/cases/linkBugs),如需完整内容请使用 fields=full';
163
+ }
108
164
  return {
109
- content: [{ type: "text", text: JSON.stringify(bug, null, 2) }]
165
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
110
166
  };
111
167
  });
112
168
  // Add resolveBug tool
@@ -360,13 +416,31 @@ server.tool("getProjectReleases", {
360
416
  });
361
417
  // Add getStoryDetail tool
362
418
  server.tool("getStoryDetail", {
363
- storyId: z.number()
364
- }, async ({ storyId }) => {
365
- if (!zentaoApi)
366
- throw new Error("Please initialize Zentao API first");
367
- const story = await zentaoApi.getStoryDetail(storyId);
419
+ storyId: z.number(),
420
+ fields: z.enum(['basic', 'detail', 'full']).optional().describe(`
421
+ 字段级别:
422
+ - basic: 基本信息(id, title, status, stage, pri等核心字段)
423
+ - detail: 详细信息(包含spec和verify,但不包含executions/tasks/stages/children等关联数据)
424
+ - full: 完整信息(包含所有字段,默认值)
425
+ `.trim())
426
+ }, async ({ storyId, fields }) => {
427
+ if (!zentaoApi)
428
+ throw new Error("Please initialize Zentao API first");
429
+ const story = await zentaoApi.getStoryDetail(storyId, fields);
430
+ // 添加说明信息
431
+ const result = {
432
+ fieldLevel: fields || 'full',
433
+ story: story
434
+ };
435
+ // 根据字段级别添加提示
436
+ if (fields === 'basic') {
437
+ result.note = '仅显示基本信息,如需完整内容请使用 fields=detail 或 fields=full';
438
+ }
439
+ else if (fields === 'detail') {
440
+ result.note = '显示详细信息但不包含关联数据(executions/tasks/stages/children),如需完整内容请使用 fields=full';
441
+ }
368
442
  return {
369
- content: [{ type: "text", text: JSON.stringify(story, null, 2) }]
443
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
370
444
  };
371
445
  });
372
446
  // Add createBug tool
@@ -507,7 +581,13 @@ server.tool("changeStory", {
507
581
  comment: z.string().optional()
508
582
  }),
509
583
  z.string()
510
- ]).describe("需求变更字段 - 支持对象或JSON字符串格式")
584
+ ]).describe(`修改需求字段(使用最新 PUT 接口)
585
+
586
+ 此工具使用"修改需求其他字段"接口(PUT /stories/:id),支持修改29个字段。
587
+ 已废弃旧的"变更需求"接口(POST /stories/:id/change),该接口仅支持3个字段。
588
+
589
+ 支持对象或JSON字符串格式。
590
+ `.trim())
511
591
  }, async ({ storyId, update }) => {
512
592
  if (!zentaoApi)
513
593
  throw new Error("Please initialize Zentao API first");
@@ -542,13 +622,26 @@ server.tool("deleteTask", {
542
622
  });
543
623
  // Add getProductStories tool
544
624
  server.tool("getProductStories", {
545
- productId: z.number()
546
- }, async ({ productId }) => {
547
- if (!zentaoApi)
548
- throw new Error("Please initialize Zentao API first");
549
- const stories = await zentaoApi.getProductStories(productId);
625
+ productId: z.number(),
626
+ page: z.number().optional().describe("页码,从1开始,默认为1"),
627
+ limit: z.number().optional().describe("每页数量,默认20,最大100")
628
+ }, async ({ productId, page, limit }) => {
629
+ if (!zentaoApi)
630
+ throw new Error("Please initialize Zentao API first");
631
+ const result = await zentaoApi.getProductStories(productId, page, limit);
632
+ // 返回分页信息和数据
633
+ const summary = {
634
+ summary: `当前第 ${result.page} 页,共 ${result.total} 条需求,本页显示 ${result.stories.length} 条`,
635
+ pagination: {
636
+ page: result.page,
637
+ limit: result.limit,
638
+ total: result.total,
639
+ totalPages: Math.ceil(result.total / result.limit)
640
+ },
641
+ stories: result.stories
642
+ };
550
643
  return {
551
- content: [{ type: "text", text: JSON.stringify(stories, null, 2) }]
644
+ content: [{ type: "text", text: JSON.stringify(summary, null, 2) }]
552
645
  };
553
646
  });
554
647
  // Program tools
@@ -5,8 +5,8 @@ export class BugService {
5
5
  this.api = api;
6
6
  }
7
7
  async getMyBugs(status) {
8
- const bugs = await this.api.getMyBugs(status);
9
- return bugs.map(bug => this.enrichBugData(bug));
8
+ const result = await this.api.getMyBugs(status);
9
+ return result.bugs.map((bug) => this.enrichBugData(bug));
10
10
  }
11
11
  async getBugDetail(bugId) {
12
12
  const bug = await this.api.getBugDetail(bugId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zzp123/mcp-zentao",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "禅道项目管理系统的高级API集成包,提供任务管理、Bug跟踪等功能的完整封装,专为Cursor IDE设计的MCP扩展",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",