@zzp123/mcp-zentao 1.9.0 → 1.11.0

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 CHANGED
@@ -5,6 +5,67 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.11.0] - 2025-11-07
9
+
10
+ ### Added
11
+ - **完整的 Bug 状态筛选支持** ✨
12
+ - 扩展 `BugStatus` 类型,新增 16 种状态筛选类型
13
+ - 支持所有禅道官方 API 的状态筛选:assigntome、openedbyme、resolvedbyme、assignedbyme、assigntonull、unconfirmed、unclosed、unresolved、toclosed、postponedbugs、longlifebugs、overduebugs、review、feedback、needconfirm、bysearch
14
+ - MCP 工具现在提供完整的 20 种状态选项供选择
15
+
16
+ - **分支筛选功能** 🌿
17
+ - 为 `getMyBugs` 和 `getProductBugs` 添加 `branch` 参数
18
+ - 支持 'all'(所有分支)、'0'(主干分支)、分支ID
19
+ - 默认值为 'all',符合禅道官方 API 规范
20
+
21
+ ### Changed
22
+ - **重构 Bug 列表 API 参数** 🔄
23
+ - 移除 `onlyAssignedToMe` 参数,改用更灵活的 `status='assigntome'`
24
+ - 调整参数顺序:`status, productId, page, limit, branch, order`
25
+ - 优化参数构建逻辑,确保与禅道官方 API 完全一致
26
+
27
+ ### Improved
28
+ - **API 文档完善**
29
+ - 为 `getMyBugs` 和 `getProductBugs` 添加详细的 JSDoc 注释
30
+ - 更新工具描述,明确说明支持的状态筛选类型
31
+ - 参数说明更加清晰,包含默认值和示例
32
+
33
+ - **更好的用户体验**
34
+ - MCP 工具提供完整的状态枚举,IDE 可以自动补全
35
+ - 所有状态选项都有中文注释说明
36
+ - 更符合禅道用户的使用习惯
37
+
38
+ ### Technical
39
+ - 完全符合禅道官方 Bug 列表 API 文档规范
40
+ - 支持所有官方 API 参数:branch、status、order、limit、page
41
+ - 默认值与官方 API 保持一致
42
+
43
+ ## [1.10.0] - 2025-11-07
44
+
45
+ ### Fixed
46
+ - **修复 Bug 获取工具的 API 端点和参数问题** 🔧
47
+ - 从错误的 `/bugs` 端点改为正确的 `/products/{productId}/bugs` 端点
48
+ - 移除了低效的客户端过滤逻辑,改用服务器端原生支持的 `assigntome` 状态
49
+ - 修复了 `onlyAssignedToMe` 参数无法正确工作的问题
50
+
51
+ ### Added
52
+ - **添加排序参数支持** ✨
53
+ - 为 `getMyBugs` 和 `getProductBugs` 工具添加 `order` 参数
54
+ - 支持多种排序方式:`id_desc`(ID降序)、`id_asc`(ID升序)、`pri_desc`(优先级降序)等
55
+ - 默认使用 `id_desc` 排序,确保最新的 Bug 显示在前面
56
+ - 与禅道官方 API 完全一致的参数格式
57
+
58
+ ### Improved
59
+ - **性能优化**
60
+ - 使用服务器端过滤替代客户端过滤,显著提升查询效率
61
+ - 减少不必要的数据传输,降低网络开销
62
+ - 更准确的分页信息和总数统计
63
+
64
+ ### Changed
65
+ - 更新了 `getMyBugs` 方法签名,添加 `order` 参数
66
+ - 更新了 `getProductBugs` 方法签名,添加 `order` 参数
67
+ - 优化了查询参数的构建方式,使用 URLSearchParams 确保参数正确编码
68
+
8
69
  ## [1.9.0] - 2025-11-06
9
70
 
10
71
  ### Fixed
package/README.md CHANGED
@@ -15,17 +15,17 @@ npm install @zzp123/mcp-zentao -g
15
15
 
16
16
  查看完整的版本更新历史,请访问 [CHANGELOG.md](./CHANGELOG.md)
17
17
 
18
- **最新版本**: v1.6.1
19
- - 修复 Windows 剪贴板图片读取问题
20
- - 使用新的 PowerShell 脚本确保正确读取剪贴板图片
21
- - 改进错误处理和日志输出
22
-
23
- **主要版本**:
24
- - v1.4.1 - 修复 uploadImageFromClipboard 返回值问题
18
+ **最新版本**: v1.9.0
19
+ - 修复 getMyBugs 工具的过滤逻辑问题
20
+ - 改为客户端过滤,正确获取当前用户的bug
21
+ - ✅ 支持 assignedTo 的对象和字符串两种格式
22
+
23
+ **近期版本**:
24
+ - v1.8.9 - 修复 resolveBug 工具自动设置默认 resolvedBuild
25
+ - v1.8.8 - 修复 getMyBugs 工具返回Bug数量为0的问题
26
+ - v1.6.1 - 修复 Windows 剪贴板图片读取问题
25
27
  - v1.4.0 - 新增解决Bug接口
26
- - v1.3.1 - 修复剪贴板图片上传核心问题
27
28
  - v1.3.0 - 添加命令行脚本支持
28
- - v1.2.0 - 添加文件上传/下载功能
29
29
 
30
30
  ## 使用方法
31
31
 
@@ -9,13 +9,35 @@ 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, page?: number, limit?: number, onlyAssignedToMe?: boolean): Promise<{
12
+ /**
13
+ * 获取 Bug 列表
14
+ *
15
+ * @param status Bug状态筛选(支持多种状态,详见BugStatus类型)
16
+ * @param productId 产品ID(可选,默认使用第二个产品)
17
+ * @param page 页码(从1开始,默认1)
18
+ * @param limit 每页数量(默认20,最大100)
19
+ * @param branch 分支筛选('all'所有分支/'0'主干/分支ID,默认'all')
20
+ * @param order 排序字段(如:'id_desc'、'pri_desc'等,默认'id_desc')
21
+ * @returns Promise<Bug列表及分页信息>
22
+ */
23
+ getMyBugs(status?: BugStatus, productId?: number, page?: number, limit?: number, branch?: string, order?: string): Promise<{
13
24
  page: number;
14
25
  total: number;
15
26
  limit: number;
16
27
  bugs: Bug[];
17
28
  }>;
18
- getProductBugs(productId: number, page?: number, limit?: number, status?: BugStatus): Promise<{
29
+ /**
30
+ * 获取产品 Bug 列表
31
+ *
32
+ * @param productId 产品ID(必填)
33
+ * @param page 页码(从1开始,默认1)
34
+ * @param limit 每页数量(默认20,最大100)
35
+ * @param status Bug状态筛选(支持多种状态,详见BugStatus类型)
36
+ * @param branch 分支筛选('all'所有分支/'0'主干/分支ID,默认'all')
37
+ * @param order 排序字段(如:'id_desc'、'pri_desc'等,默认'id_desc')
38
+ * @returns Promise<Bug列表及分页信息>
39
+ */
40
+ getProductBugs(productId: number, page?: number, limit?: number, status?: BugStatus, branch?: string, order?: string): Promise<{
19
41
  page: number;
20
42
  total: number;
21
43
  limit: number;
@@ -111,7 +111,18 @@ export class ZentaoAPI {
111
111
  throw error;
112
112
  }
113
113
  }
114
- async getMyBugs(status, productId, page, limit, onlyAssignedToMe = false) {
114
+ /**
115
+ * 获取 Bug 列表
116
+ *
117
+ * @param status Bug状态筛选(支持多种状态,详见BugStatus类型)
118
+ * @param productId 产品ID(可选,默认使用第二个产品)
119
+ * @param page 页码(从1开始,默认1)
120
+ * @param limit 每页数量(默认20,最大100)
121
+ * @param branch 分支筛选('all'所有分支/'0'主干/分支ID,默认'all')
122
+ * @param order 排序字段(如:'id_desc'、'pri_desc'等,默认'id_desc')
123
+ * @returns Promise<Bug列表及分页信息>
124
+ */
125
+ async getMyBugs(status, productId, page, limit, branch, order) {
115
126
  if (!productId) {
116
127
  // 如果没有提供产品ID,获取第二个可用的产品(索引为1)
117
128
  const products = await this.getProducts();
@@ -125,72 +136,74 @@ export class ZentaoAPI {
125
136
  // 默认每页20条,最多100条
126
137
  const finalLimit = limit ? Math.min(limit, 100) : 20;
127
138
  const finalPage = page || 1;
128
- // 禅道API的/bugs接口不支持assignedTo参数过滤,需要客户端过滤
129
- // 所以获取更多数据以确保有足够的结果
130
- const fetchLimit = onlyAssignedToMe ? 100 : finalLimit;
131
- const params = {
132
- status: status || 'all',
133
- product: productId,
134
- page: 1,
135
- limit: fetchLimit
136
- };
137
- try {
138
- console.log(`正在获取Bug列表,参数:`, params);
139
- const response = await this.request('GET', '/bugs', params);
139
+ const finalBranch = branch || 'all';
140
+ const finalOrder = order || 'id_desc';
141
+ // 构建查询参数
142
+ const queryParams = new URLSearchParams();
143
+ queryParams.append('branch', finalBranch);
144
+ queryParams.append('page', finalPage.toString());
145
+ queryParams.append('limit', finalLimit.toString());
146
+ queryParams.append('order', finalOrder);
147
+ // 添加状态筛选
148
+ if (status && status !== 'all') {
149
+ queryParams.append('status', status);
150
+ }
151
+ try {
152
+ console.log(`正在获取产品 ${productId} 的Bug列表,参数: status=${status || 'all'}, branch=${finalBranch}, page=${finalPage}, limit=${finalLimit}, order=${finalOrder}`);
153
+ const response = await this.request('GET', `/products/${productId}/bugs?${queryParams.toString()}`);
140
154
  console.log(`Bug列表响应: 获取到 ${response.bugs?.length || 0} 条数据`);
141
- let bugs = [];
142
155
  if (Array.isArray(response)) {
143
- bugs = response;
144
- }
145
- else if (response && typeof response === 'object' && Array.isArray(response.bugs)) {
146
- bugs = response.bugs;
147
- }
148
- else {
149
- throw new Error(`获取Bug列表失败: 响应格式不正确 ${JSON.stringify(response)}`);
156
+ // 如果返回的是数组,转换为标准格式
157
+ return {
158
+ page: finalPage,
159
+ total: response.length,
160
+ limit: finalLimit,
161
+ bugs: response
162
+ };
150
163
  }
151
- // 如果需要过滤分配给当前用户的bug,在客户端进行过滤
152
- if (onlyAssignedToMe) {
153
- console.log(`客户端过滤:只保留分配给 ${this.config.username} 的Bug`);
154
- bugs = bugs.filter(bug => {
155
- // assignedTo可能是对象或字符串
156
- if (typeof bug.assignedTo === 'object' && bug.assignedTo !== null) {
157
- return bug.assignedTo.account === this.config.username;
158
- }
159
- else if (typeof bug.assignedTo === 'string') {
160
- return bug.assignedTo === this.config.username;
161
- }
162
- return false;
163
- });
164
- console.log(`过滤后剩余 ${bugs.length} 个Bug`);
164
+ else if (response && typeof response === 'object') {
165
+ if (Array.isArray(response.bugs)) {
166
+ return {
167
+ page: response.page || finalPage,
168
+ total: response.total || response.bugs.length,
169
+ limit: response.limit || finalLimit,
170
+ bugs: response.bugs
171
+ };
172
+ }
165
173
  }
166
- // 应用分页
167
- const start = (finalPage - 1) * finalLimit;
168
- const paginatedBugs = bugs.slice(start, start + finalLimit);
169
- return {
170
- page: finalPage,
171
- total: bugs.length,
172
- limit: finalLimit,
173
- bugs: paginatedBugs
174
- };
174
+ throw new Error(`获取Bug列表失败: 响应格式不正确 ${JSON.stringify(response)}`);
175
175
  }
176
176
  catch (error) {
177
- if (error instanceof Error && error.message.includes('Need product id')) {
178
- throw new Error('获取Bug列表失败: 请提供产品ID');
179
- }
180
177
  console.error('获取Bug列表失败:', error);
181
178
  throw error;
182
179
  }
183
180
  }
184
- async getProductBugs(productId, page, limit, status) {
181
+ /**
182
+ * 获取产品 Bug 列表
183
+ *
184
+ * @param productId 产品ID(必填)
185
+ * @param page 页码(从1开始,默认1)
186
+ * @param limit 每页数量(默认20,最大100)
187
+ * @param status Bug状态筛选(支持多种状态,详见BugStatus类型)
188
+ * @param branch 分支筛选('all'所有分支/'0'主干/分支ID,默认'all')
189
+ * @param order 排序字段(如:'id_desc'、'pri_desc'等,默认'id_desc')
190
+ * @returns Promise<Bug列表及分页信息>
191
+ */
192
+ async getProductBugs(productId, page, limit, status, branch, order) {
185
193
  try {
186
194
  // 默认每页20条,最多100条
187
195
  const finalLimit = limit ? Math.min(limit, 100) : 20;
188
196
  const finalPage = page || 1;
189
- console.log(`正在获取产品 ${productId} 的Bug列表 (page=${finalPage}, limit=${finalLimit})...`);
197
+ const finalBranch = branch || 'all';
198
+ const finalOrder = order || 'id_desc';
199
+ console.log(`正在获取产品 ${productId} 的Bug列表 (status=${status || 'all'}, branch=${finalBranch}, page=${finalPage}, limit=${finalLimit}, order=${finalOrder})...`);
190
200
  // 构建查询参数
191
201
  const queryParams = new URLSearchParams();
202
+ queryParams.append('branch', finalBranch);
192
203
  queryParams.append('page', finalPage.toString());
193
204
  queryParams.append('limit', finalLimit.toString());
205
+ queryParams.append('order', finalOrder);
206
+ // 添加状态筛选
194
207
  if (status && status !== 'all') {
195
208
  queryParams.append('status', status);
196
209
  }
package/dist/index.js CHANGED
@@ -87,16 +87,38 @@ server.tool("getProducts", {}, async () => {
87
87
  };
88
88
  });
89
89
  // Add getMyBugs tool
90
- server.tool("getMyBugs", "获取Bug列表 - 默认获取所有Bug,可选择只获取分配给我的Bug", {
91
- status: z.enum(['active', 'resolved', 'closed', 'all']).optional(),
92
- productId: z.number().optional(),
90
+ server.tool("getMyBugs", "获取Bug列表 - 支持多种状态筛选(指派给我、由我创建、未关闭等)和分支筛选", {
91
+ status: z.enum([
92
+ 'active', // 激活状态
93
+ 'resolved', // 已解决
94
+ 'closed', // 已关闭
95
+ 'all', // 所有状态
96
+ 'assigntome', // 指派给我的
97
+ 'openedbyme', // 由我创建
98
+ 'resolvedbyme', // 由我解决
99
+ 'assignedbyme', // 由我指派
100
+ 'assigntonull', // 未指派
101
+ 'unconfirmed', // 未确认
102
+ 'unclosed', // 未关闭
103
+ 'unresolved', // 未解决(激活状态)
104
+ 'toclosed', // 待关闭(已解决)
105
+ 'postponedbugs', // 延期的Bug
106
+ 'longlifebugs', // 长期未处理的Bug
107
+ 'overduebugs', // 已过期的Bug
108
+ 'review', // 待我审核
109
+ 'feedback', // 用户反馈
110
+ 'needconfirm', // 需求变更需确认
111
+ 'bysearch' // 自定义搜索
112
+ ]).optional().describe("Bug状态筛选,默认返回所有状态"),
113
+ productId: z.number().optional().describe("产品ID,默认使用第二个产品"),
93
114
  page: z.number().optional().describe("页码,从1开始,默认为1"),
94
115
  limit: z.number().optional().describe("每页数量,默认20,最大100"),
95
- onlyAssignedToMe: z.boolean().optional().describe("是否只获取分配给我的Bug,默认false(获取所有Bug)")
96
- }, async ({ status, productId, page, limit, onlyAssignedToMe }) => {
116
+ branch: z.string().optional().describe("分支筛选:'all'(所有分支,默认)、'0'(主干分支)、分支ID"),
117
+ order: z.string().optional().describe("排序方式,如 id_desc(ID降序), pri_desc(优先级降序), openedDate_desc(创建时间降序)等,默认id_desc")
118
+ }, async ({ status, productId, page, limit, branch, order }) => {
97
119
  if (!zentaoApi)
98
120
  throw new Error("Please initialize Zentao API first");
99
- const result = await zentaoApi.getMyBugs(status, productId, page, limit, onlyAssignedToMe || false);
121
+ const result = await zentaoApi.getMyBugs(status, productId, page, limit, branch, order);
100
122
  // 返回分页信息和数据
101
123
  const summary = {
102
124
  summary: `当前第 ${result.page} 页,共 ${result.total} 个Bug,本页显示 ${result.bugs.length} 个`,
@@ -113,15 +135,38 @@ server.tool("getMyBugs", "获取Bug列表 - 默认获取所有Bug,可选择只
113
135
  };
114
136
  });
115
137
  // Add getProductBugs tool
116
- server.tool("getProductBugs", {
117
- productId: z.number(),
138
+ server.tool("getProductBugs", "获取指定产品的Bug列表 - 支持多种状态筛选和分支筛选", {
139
+ productId: z.number().describe("产品ID(必填)"),
118
140
  page: z.number().optional().describe("页码,从1开始,默认为1"),
119
141
  limit: z.number().optional().describe("每页数量,默认20,最大100"),
120
- status: z.enum(['active', 'resolved', 'closed', 'all']).optional()
121
- }, async ({ productId, page, limit, status }) => {
122
- if (!zentaoApi)
123
- throw new Error("Please initialize Zentao API first");
124
- const result = await zentaoApi.getProductBugs(productId, page, limit, status);
142
+ status: z.enum([
143
+ 'active', // 激活状态
144
+ 'resolved', // 已解决
145
+ 'closed', // 已关闭
146
+ 'all', // 所有状态
147
+ 'assigntome', // 指派给我的
148
+ 'openedbyme', // 由我创建
149
+ 'resolvedbyme', // 由我解决
150
+ 'assignedbyme', // 由我指派
151
+ 'assigntonull', // 未指派
152
+ 'unconfirmed', // 未确认
153
+ 'unclosed', // 未关闭
154
+ 'unresolved', // 未解决(激活状态)
155
+ 'toclosed', // 待关闭(已解决)
156
+ 'postponedbugs', // 延期的Bug
157
+ 'longlifebugs', // 长期未处理的Bug
158
+ 'overduebugs', // 已过期的Bug
159
+ 'review', // 待我审核
160
+ 'feedback', // 用户反馈
161
+ 'needconfirm', // 需求变更需确认
162
+ 'bysearch' // 自定义搜索
163
+ ]).optional().describe("Bug状态筛选,默认返回所有状态"),
164
+ branch: z.string().optional().describe("分支筛选:'all'(所有分支,默认)、'0'(主干分支)、分支ID"),
165
+ order: z.string().optional().describe("排序方式,如 id_desc(ID降序), pri_desc(优先级降序), openedDate_desc(创建时间降序)等,默认id_desc")
166
+ }, async ({ productId, page, limit, status, branch, order }) => {
167
+ if (!zentaoApi)
168
+ throw new Error("Please initialize Zentao API first");
169
+ const result = await zentaoApi.getProductBugs(productId, page, limit, status, branch, order);
125
170
  // 返回分页信息和数据
126
171
  const summary = {
127
172
  summary: `当前第 ${result.page} 页,共 ${result.total} 个Bug,本页显示 ${result.bugs.length} 个`,
@@ -56,7 +56,34 @@ export interface TaskUpdate {
56
56
  comment?: string;
57
57
  }
58
58
  export type TaskStatus = 'wait' | 'doing' | 'done' | 'all';
59
- export type BugStatus = 'active' | 'resolved' | 'closed' | 'all';
59
+ /**
60
+ * Bug 状态筛选类型
61
+ *
62
+ * 基础状态:
63
+ * - active: 激活状态
64
+ * - resolved: 已解决
65
+ * - closed: 已关闭
66
+ * - all: 所有状态
67
+ *
68
+ * 筛选条件:
69
+ * - assigntome: 指派给我的
70
+ * - openedbyme: 由我创建
71
+ * - resolvedbyme: 由我解决
72
+ * - assignedbyme: 由我指派
73
+ * - assigntonull: 未指派
74
+ * - unconfirmed: 未确认
75
+ * - unclosed: 未关闭
76
+ * - unresolved: 未解决(激活状态)
77
+ * - toclosed: 待关闭(已解决)
78
+ * - postponedbugs: 延期的Bug
79
+ * - longlifebugs: 长期未处理的Bug
80
+ * - overduebugs: 已过期的Bug
81
+ * - review: 待我审核
82
+ * - feedback: 用户反馈
83
+ * - needconfirm: 需求变更需确认
84
+ * - bysearch: 自定义搜索(需配合queryID使用)
85
+ */
86
+ export type BugStatus = 'active' | 'resolved' | 'closed' | 'all' | 'assigntome' | 'openedbyme' | 'resolvedbyme' | 'assignedbyme' | 'assigntonull' | 'unconfirmed' | 'unclosed' | 'unresolved' | 'toclosed' | 'postponedbugs' | 'longlifebugs' | 'overduebugs' | 'review' | 'feedback' | 'needconfirm' | 'bysearch';
60
87
  export type ResolutionType = 'fixed' | 'bydesign' | 'duplicate' | 'external' | 'notrepro' | 'postponed' | 'willnotfix';
61
88
  export interface ResolveBugRequest {
62
89
  resolution: ResolutionType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zzp123/mcp-zentao",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "禅道项目管理系统的高级API集成包,提供任务管理、Bug跟踪等功能的完整封装,专为Cursor IDE设计的MCP扩展",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",