@dyno181cm.nexsoft/zentao_mcp 1.5.0 → 1.6.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/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen?style=flat-square&logo=node.js)](https://nodejs.org)
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?style=flat-square&logo=typescript)](https://www.typescriptlang.org)
9
9
  [![MCP](https://img.shields.io/badge/MCP-compatible-8A2BE2?style=flat-square)](https://modelcontextprotocol.io)
10
+ [![Tests](https://img.shields.io/badge/tests-26%20passed-brightgreen?style=flat-square&logo=jest)](https://github.com/dyno-nexsoft/zentao_mcp/tree/master/tests)
10
11
 
11
12
  A **Model Context Protocol (MCP)** server for integrating AI assistants (Claude, Cursor, etc.) with the [ZenTao](https://www.zentao.net) project management API.
12
13
  Fetch task details, bug reports, and attachments — all directly inside your AI chat.
@@ -21,6 +22,7 @@ Fetch task details, bug reports, and attachments — all directly inside your AI
21
22
  | `zentao_get_bug_details` | Fetch full details of a bug by ID (severity, repro steps, history & comments, inline images, attachments) |
22
23
  | `zentao_download_attachment` | Download any ZenTao file attachment to local disk |
23
24
  | `zentao_get_comments` | Fetch only the history and comments timeline of a task or bug |
25
+ | `zentao_add_comment` | Add a comment/remark to a task or bug |
24
26
  | `zentao_update_task_status` | Update task status (start, finish, close, pause, cancel, restart) and add optional comments/hours |
25
27
  | `zentao_update_bug_status` | Update bug status (resolve, close, activate) and add optional comments/resolutions |
26
28
 
@@ -28,9 +30,11 @@ Fetch task details, bug reports, and attachments — all directly inside your AI
28
30
  **Under the hood:**
29
31
  - 🔐 Auto login & token refresh — no manual auth needed
30
32
  - 📦 In-memory cache (2 min TTL) + in-flight request dedup — avoids redundant API calls
33
+ - 🔁 Classic JSON API fallback — seamlessly retries via the web API when the REST endpoint returns empty or errors
31
34
  - 🖼️ Inline HTML images are automatically downloaded and served as local `file://` links
32
35
  - 📎 Attachments are downloaded and embedded as clickable local links
33
36
  - 🛡️ Corrupt partial downloads are auto-cleaned on error
37
+ - ⚡ Non-blocking async image I/O — base64 encoding uses `fs.promises` + `Promise.all`
34
38
 
35
39
  ---
36
40
 
@@ -130,6 +134,15 @@ Get only the history and comments timeline of a task or bug.
130
134
  | `type` | `"task" \| "bug"` | ✅ | Type of the object |
131
135
  | `id` | `string \| number` | ✅ | Task or Bug ID |
132
136
 
137
+ ### `zentao_add_comment`
138
+ Add a comment/remark to a task or bug.
139
+
140
+ | Parameter | Type | Required | Description |
141
+ |---|---|---|---|
142
+ | `type` | `"task" \| "bug"` | ✅ | Type of the object |
143
+ | `id` | `string \| number` | ✅ | Task or Bug ID |
144
+ | `comment` | `string` | ✅ | Comment content |
145
+
133
146
  ### `zentao_update_task_status`
134
147
  Update a task's status with optional comments, actual start/finish dates, and consumed hours.
135
148
 
@@ -174,24 +187,31 @@ npm run test:api # Live API integration test
174
187
  ```
175
188
  src/
176
189
  ├── index.ts # MCP server entry point
177
- ├── zentaoClient.ts # Axios client: auth, cache, dedup, status updates
190
+ ├── zentaoClient.ts # Axios client: auth, cache, dedup, fallback, stream helpers
178
191
  ├── tools.ts # Thin orchestrator + backward-compat exports
179
192
  ├── utils/
180
193
  │ ├── fileUtils.ts # toFileUrl · getMimeType · formatSize
181
194
  │ ├── markdownUtils.ts # htmlToMarkdown · parseFiles · formatUser
182
- │ └── mcpResponse.ts # mcpText · buildMcpResponse
195
+ │ └── mcpResponse.ts # mcpText · buildMcpResponse (async)
183
196
  ├── formatters/
184
- │ ├── imageLocalizer.ts # Inline <img> → local file:// link
197
+ │ ├── imageLocalizer.ts # Inline <img> → local file:// link (deduped, concurrent)
185
198
  │ ├── attachmentRenderer.ts # Attachments → Markdown ## Files section
186
199
  │ ├── actionFormatter.ts # renderHistoryAndComments
187
200
  │ ├── taskFormatter.ts # taskToMarkdown (includes comments)
188
201
  │ └── bugFormatter.ts # bugToMarkdown (includes comments)
189
202
  └── tools/
190
- ├── taskTool.ts # zentao_get_task_details registration
191
- ├── bugTool.ts # zentao_get_bug_details registration
192
- ├── downloadTool.ts # zentao_download_attachment registration
193
- ├── commentTool.ts # zentao_get_comments registration
194
- └── statusTool.ts # status update tools registration
203
+ ├── taskTool.ts # zentao_get_task_details
204
+ ├── bugTool.ts # zentao_get_bug_details
205
+ ├── downloadTool.ts # zentao_download_attachment
206
+ ├── commentTool.ts # zentao_get_comments · zentao_add_comment
207
+ └── statusTool.ts # zentao_update_task_status · zentao_update_bug_status
208
+ ```
209
+
210
+ ### Running tests
211
+
212
+ ```bash
213
+ npm test # Unit tests — 26 tests across ZentaoClient + formatters
214
+ npm run test:api # Live API integration test (requires .env)
195
215
  ```
196
216
 
197
217
  ---
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Format a raw ZenTao "my work" response object into a human-readable Markdown document.
3
+ *
4
+ * @param myWork The raw "my work" object containing profile, task, and bug lists.
5
+ * @param client Authenticated ZentaoClient to get baseUrl for linking.
6
+ */
7
+ export function myWorkToMarkdown(myWork, client) {
8
+ if (!myWork)
9
+ return "No work items found.";
10
+ const profile = myWork.profile || {};
11
+ const taskData = myWork.task || { total: 0, tasks: [] };
12
+ const bugData = myWork.bug || { total: 0, bugs: [] };
13
+ const webUrl = client.webUrl || "";
14
+ const header = [
15
+ `# Work Items Assigned to ${profile.realname || profile.account || "Me"}`,
16
+ `- **Account:** ${profile.account || "N/A"}`,
17
+ `- **Role:** ${profile.role?.name || "N/A"}`,
18
+ `- **Total Tasks:** ${taskData.total}`,
19
+ `- **Total Bugs:** ${bugData.total}`,
20
+ ""
21
+ ];
22
+ const sections = [...header];
23
+ // ── Tasks Section ──
24
+ sections.push("## Tasks Assigned to Me");
25
+ if (taskData.total === 0 || !taskData.tasks || taskData.tasks.length === 0) {
26
+ sections.push("*No tasks assigned to you.*", "");
27
+ }
28
+ else {
29
+ sections.push("| ID | Task Name | Pri | Status | Execution | Deadline | Progress |", "| :--- | :--- | :---: | :---: | :--- | :---: | :---: |");
30
+ for (const t of taskData.tasks) {
31
+ const taskLink = webUrl ? `[${t.id}](${webUrl}/task-view-${t.id}.html)` : `${t.id}`;
32
+ const execution = t.executionName || "N/A";
33
+ const prog = t.progress ?? 0;
34
+ const deadline = t.deadline || "N/A";
35
+ sections.push(`| ${taskLink} | ${t.name} | ${t.pri} | ${t.status} | ${execution} | ${deadline} | ${prog}% |`);
36
+ }
37
+ sections.push("");
38
+ }
39
+ // ── Bugs Section ──
40
+ sections.push("## Bugs Assigned to Me");
41
+ if (bugData.total === 0 || !bugData.bugs || bugData.bugs.length === 0) {
42
+ sections.push("*No bugs assigned to you.*", "");
43
+ }
44
+ else {
45
+ sections.push("| ID | Bug Title | Pri | Status | Product | Deadline |", "| :--- | :--- | :---: | :---: | :--- | :---: |");
46
+ for (const b of bugData.bugs) {
47
+ const bugLink = webUrl ? `[${b.id}](${webUrl}/bug-view-${b.id}.html)` : `${b.id}`;
48
+ const product = b.productName || "N/A";
49
+ const deadline = b.deadline || "N/A";
50
+ sections.push(`| ${bugLink} | ${b.title} | ${b.pri} | ${b.status} | ${product} | ${deadline} |`);
51
+ }
52
+ sections.push("");
53
+ }
54
+ return sections.join("\n");
55
+ }
@@ -0,0 +1,18 @@
1
+ import { myWorkToMarkdown } from "../formatters/myWorkFormatter.js";
2
+ import { mcpText } from "../utils/mcpResponse.js";
3
+ /**
4
+ * Register the `zentao_get_assigned_to_me` MCP tool on the given server.
5
+ *
6
+ * @param server The MCP Server instance.
7
+ * @param client Authenticated ZentaoClient used to fetch and process data.
8
+ */
9
+ export function registerMyWorkTool(server, client) {
10
+ server.registerTool("zentao_get_assigned_to_me", {
11
+ description: "Get tasks and bugs assigned to the current user (configured in environment variables)",
12
+ inputSchema: {},
13
+ }, async () => {
14
+ const data = await client.getMyWork();
15
+ const markdown = myWorkToMarkdown(data, client);
16
+ return mcpText(markdown);
17
+ });
18
+ }
package/build/tools.js CHANGED
@@ -18,6 +18,7 @@ import { registerBugTool } from "./tools/bugTool.js";
18
18
  import { registerDownloadTool } from "./tools/downloadTool.js";
19
19
  import { registerCommentTool } from "./tools/commentTool.js";
20
20
  import { registerStatusTools } from "./tools/statusTool.js";
21
+ import { registerMyWorkTool } from "./tools/myWorkTool.js";
21
22
  // ─── Shared singleton ────────────────────────────────────────────────────────
22
23
  const client = new ZentaoClient();
23
24
  // ─── Backward-compatible re-exports ──────────────────────────────────────────
@@ -51,4 +52,5 @@ export function registerTools(server) {
51
52
  registerDownloadTool(server, client);
52
53
  registerCommentTool(server, client);
53
54
  registerStatusTools(server, client);
55
+ registerMyWorkTool(server, client);
54
56
  }
@@ -52,6 +52,12 @@ export class ZentaoClient {
52
52
  }
53
53
  return this.baseUrl.split('/api/v1')[0];
54
54
  }
55
+ /**
56
+ * Public getter exposing the web base URL.
57
+ */
58
+ get webUrl() {
59
+ return this.webBaseUrl;
60
+ }
55
61
  // ─── Cache helpers ────────────────────────────────────────────────────────────
56
62
  /**
57
63
  * Clears the in-memory cache for GET requests. Helpful for testing.
@@ -304,6 +310,14 @@ export class ZentaoClient {
304
310
  });
305
311
  }
306
312
  // ─── Public API ───────────────────────────────────────────────────────────────
313
+ /**
314
+ * Retrieves tasks and bugs assigned to the current user (my work).
315
+ *
316
+ * @returns Resolves with the user's work items (tasks and bugs).
317
+ */
318
+ async getMyWork() {
319
+ return this.get('/user?fields=task,bug&type=assignedTo');
320
+ }
307
321
  /**
308
322
  * Retrieves details of a specific task.
309
323
  * Checks the cache first, otherwise fetches from API.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyno181cm.nexsoft/zentao_mcp",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Zentao MCP Server",
5
5
  "repository": {
6
6
  "type": "git",