@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 +28 -8
- package/build/formatters/myWorkFormatter.js +55 -0
- package/build/tools/myWorkTool.js +18 -0
- package/build/tools.js +2 -0
- package/build/zentaoClient.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
[](https://www.typescriptlang.org)
|
|
9
9
|
[](https://modelcontextprotocol.io)
|
|
10
|
+
[](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,
|
|
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
|
|
191
|
-
├── bugTool.ts # zentao_get_bug_details
|
|
192
|
-
├── downloadTool.ts # zentao_download_attachment
|
|
193
|
-
├── commentTool.ts # zentao_get_comments
|
|
194
|
-
└── statusTool.ts #
|
|
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
|
}
|
package/build/zentaoClient.js
CHANGED
|
@@ -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.
|