@aipper/zentao-mcp-server 0.1.11 → 0.1.13

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/.env.example CHANGED
@@ -8,6 +8,9 @@ ZENTAO_PASSWORD=your_password
8
8
  # 可选:API 前缀(默认 /api.php/v1)
9
9
  # ZENTAO_API_PREFIX=/api.php/v1
10
10
 
11
+ # 可选:默认项目 ID(直接查 /projects/{id}/bugs,不与其它项目合并)
12
+ # ZENTAO_PROJECT_ID=15
13
+
11
14
  # 可选:默认产品 ID(当实例请求 bug 列表需要 product id 时使用)
12
15
  # ZENTAO_PRODUCT_ID=1
13
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipper/zentao-mcp-server",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "A minimal MCP server for ZenTao (token + focused bug workflow tools).",
package/src/index.js CHANGED
@@ -82,6 +82,7 @@ function getConfigFromEnv() {
82
82
  const timeoutMs = Number(process.env.ZENTAO_HTTP_TIMEOUT_MS || "30000");
83
83
  const defaultProductId = Number(process.env.ZENTAO_PRODUCT_ID || "0") || null;
84
84
  const defaultProjectSetId = Number(process.env.ZENTAO_PROJECT_SET_ID || "0") || null;
85
+ const defaultProjectId = Number(process.env.ZENTAO_PROJECT_ID || "0") || null;
85
86
  const myBugsPath = String(process.env.ZENTAO_MY_BUGS_PATH || "").trim();
86
87
  const bugsFallbackPaths = String(process.env.ZENTAO_BUGS_FALLBACK_PATHS || "")
87
88
  .split(",")
@@ -104,6 +105,7 @@ function getConfigFromEnv() {
104
105
  allowInsecureHttp: ALLOW_INSECURE_HTTP,
105
106
  defaultProductId,
106
107
  defaultProjectSetId,
108
+ defaultProjectId,
107
109
  myBugsPath,
108
110
  bugsFallbackPaths,
109
111
  projectSetBugsPaths,
@@ -162,6 +164,7 @@ async function main() {
162
164
  limit: args.limit,
163
165
  page: args.page,
164
166
  productId: args.productId,
167
+ projectId: args.projectId,
165
168
  projectSetId: args.projectSetId,
166
169
  path: args.path || "/bugs",
167
170
  });
@@ -192,6 +195,7 @@ async function main() {
192
195
  limit: args.limit,
193
196
  page: args.page,
194
197
  productId: args.productId,
198
+ projectId: args.projectId,
195
199
  projectSetId: args.projectSetId,
196
200
  maxItems: args.maxItems,
197
201
  resolution: args.resolution || "fixed",
@@ -236,9 +240,9 @@ async function main() {
236
240
  ok: false,
237
241
  tool: rawToolName,
238
242
  message: String(err?.message || err),
239
- status: err?.status ?? null,
240
- hint: "If you see 'Need product id', set env ZENTAO_PRODUCT_ID or pass productId in get_my_bugs.",
241
- hint2: "For project-set instances, prefer get_my_bugs with projectSetId or ZENTAO_MY_BUGS_PATH=/my/bug; list_my_projects may miss project sets without concrete projects.",
243
+ status: err?.status ?? null,
244
+ hint: "If you see 'Need product id', set env ZENTAO_PRODUCT_ID or pass productId in get_my_bugs. For project-scoped bugs, set ZENTAO_PROJECT_ID or pass projectId.",
245
+ hint2: "For project-set instances, prefer get_my_bugs with projectSetId or ZENTAO_MY_BUGS_PATH=/my/bug; list_my_projects may miss project sets without concrete projects.",
242
246
  };
243
247
  return toMcpTextResult(JSON.stringify(errorPayload, null, 2), { isError: true });
244
248
  }
package/src/tools.js CHANGED
@@ -41,6 +41,11 @@ export const TOOLS = [
41
41
  limit: { type: "number", minimum: 1, maximum: 200, description: "Default 20, max 200" },
42
42
  page: { type: "number", minimum: 1, description: "Default 1" },
43
43
  productId: { type: "number", minimum: 1, description: "Optional product id (for instances requiring product scope)" },
44
+ projectId: {
45
+ type: "number",
46
+ minimum: 1,
47
+ description: "Optional project id. Directly query /projects/{id}/bugs, no cross-project merge.",
48
+ },
44
49
  projectSetId: {
45
50
  type: "number",
46
51
  minimum: 1,
@@ -100,6 +105,11 @@ export const TOOLS = [
100
105
  limit: { type: "number", minimum: 1, maximum: 200, description: "List page size, default 50" },
101
106
  page: { type: "number", minimum: 1, description: "Default 1" },
102
107
  productId: { type: "number", minimum: 1, description: "Optional product id (for instances requiring product scope)" },
108
+ projectId: {
109
+ type: "number",
110
+ minimum: 1,
111
+ description: "Optional project id. Directly query /projects/{id}/bugs, no cross-project merge.",
112
+ },
103
113
  projectSetId: {
104
114
  type: "number",
105
115
  minimum: 1,
@@ -196,6 +206,9 @@ export function assertToolArgs(name, args) {
196
206
  if (args.projectSetId !== undefined && (!Number.isFinite(args.projectSetId) || args.projectSetId < 1)) {
197
207
  throw new Error("get_my_bugs.projectSetId must be a number >= 1");
198
208
  }
209
+ if (args.projectId !== undefined && (!Number.isFinite(args.projectId) || args.projectId < 1)) {
210
+ throw new Error("get_my_bugs.projectId must be a number >= 1");
211
+ }
199
212
  }
200
213
  if (name === "get_bug_detail") {
201
214
  if (!Number.isFinite(args.id) || Number(args.id) < 1) {
@@ -232,6 +245,9 @@ export function assertToolArgs(name, args) {
232
245
  if (args.projectSetId !== undefined && (!Number.isFinite(args.projectSetId) || args.projectSetId < 1)) {
233
246
  throw new Error("batch_resolve_my_bugs.projectSetId must be a number >= 1");
234
247
  }
248
+ if (args.projectId !== undefined && (!Number.isFinite(args.projectId) || args.projectId < 1)) {
249
+ throw new Error("batch_resolve_my_bugs.projectId must be a number >= 1");
250
+ }
235
251
  if (args.path !== undefined) {
236
252
  if (typeof args.path !== "string") throw new Error("batch_resolve_my_bugs.path must be a string");
237
253
  if (!SAFE_BUG_LIST_PATHS.has(args.path)) {
package/src/zentao.js CHANGED
@@ -95,6 +95,7 @@ function createAbortSignal(timeoutMs) {
95
95
  * @param {number} config.timeoutMs - Request timeout in milliseconds
96
96
  * @param {number} [config.defaultProductId] - Default product ID
97
97
  * @param {number} [config.defaultProjectSetId] - Default project set ID
98
+ * @param {number} [config.defaultProjectId] - Default project ID
98
99
  * @param {string} [config.myBugsPath] - My bugs path
99
100
  * @param {string[]} [config.bugsFallbackPaths] - Bug fallback paths
100
101
  * @param {string[]} [config.projectSetBugsPaths] - Project set bug paths
@@ -113,6 +114,7 @@ export function createZenTaoClient(config) {
113
114
  allowInsecureHttp,
114
115
  defaultProductId,
115
116
  defaultProjectSetId,
117
+ defaultProjectId,
116
118
  myBugsPath,
117
119
  bugsFallbackPaths,
118
120
  projectSetBugsPaths,
@@ -748,6 +750,7 @@ export function createZenTaoClient(config) {
748
750
  limit = 20,
749
751
  page = 1,
750
752
  productId,
753
+ projectId,
751
754
  projectSetId,
752
755
  path = "/bugs",
753
756
  } = {}) {
@@ -755,6 +758,41 @@ export function createZenTaoClient(config) {
755
758
  const safePage = Math.max(1, Number(page) || 1);
756
759
  const dashboardScanLimit = Math.max(safeLimit, 100);
757
760
  const assignee = normalizeString(auth.account);
761
+ const effectiveProjectId = normalizePositiveInt(projectId) || normalizePositiveInt(defaultProjectId);
762
+
763
+ if (effectiveProjectId) {
764
+ const query = { limit: safeLimit, page: safePage, assignedTo: assignee, status: status || undefined };
765
+ try {
766
+ const resp = await call({ path: `/projects/${effectiveProjectId}/bugs`, method: "GET", query });
767
+ const bugs = parseBugsFromResponse(resp?.data);
768
+ const filtered = bugs.filter((bug) => matchesBugFilters(bug, { status, keyword, assignee }));
769
+ return {
770
+ total: filtered.length,
771
+ matched: filtered.length,
772
+ page: safePage,
773
+ limit: safeLimit,
774
+ projectId: effectiveProjectId,
775
+ assignedTo: assignee,
776
+ bugs: filtered,
777
+ raw: {
778
+ status: resp?.status ?? null,
779
+ path: `/projects/${effectiveProjectId}/bugs`,
780
+ triedPaths: [{
781
+ path: `/projects/${effectiveProjectId}/bugs`,
782
+ status: resp?.status ?? null,
783
+ total: bugs.length,
784
+ matched: filtered.length,
785
+ }],
786
+ scannedTotal: bugs.length,
787
+ paths: [{ path: `/projects/${effectiveProjectId}/bugs`, status: resp?.status ?? null, total: bugs.length, matched: filtered.length }],
788
+ merged: false,
789
+ },
790
+ };
791
+ } catch (err) {
792
+ throw err;
793
+ }
794
+ }
795
+
758
796
  const effectiveProductId = normalizePositiveInt(productId) || normalizePositiveInt(defaultProductId);
759
797
  const effectiveProjectSetId = normalizePositiveInt(projectSetId) || normalizePositiveInt(defaultProjectSetId);
760
798
  const requestedPath = normalizeBugsListPath(path);
@@ -1287,6 +1325,7 @@ export function createZenTaoClient(config) {
1287
1325
  limit = 50,
1288
1326
  page = 1,
1289
1327
  productId,
1328
+ projectId,
1290
1329
  projectSetId,
1291
1330
  maxItems = 20,
1292
1331
  resolution = "fixed",
@@ -1302,6 +1341,7 @@ export function createZenTaoClient(config) {
1302
1341
  limit,
1303
1342
  page,
1304
1343
  productId,
1344
+ projectId,
1305
1345
  projectSetId,
1306
1346
  path,
1307
1347
  });