@dyno181cm.nexsoft/zentao_mcp 1.0.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 ADDED
@@ -0,0 +1,107 @@
1
+ # Zentao MCP Server
2
+
3
+ A Model Context Protocol (MCP) server for integrating with the Zentao API. This server provides tools to access project management data from Zentao directly through MCP-compatible interfaces.
4
+
5
+ ## Features
6
+
7
+ - **Authentication**: Automatically handles login and token management for Zentao.
8
+ - **MCP Tools**:
9
+ - `zentao_get_execution_tasks`: Retrieve tasks in an execution (sprint), with optional filtering by module ID.
10
+ - `zentao_get_product_bugs`: Fetch a list of bugs associated with a specific product.
11
+ - `zentao_get_bug_details`: Get detailed information about a specific bug.
12
+
13
+ ## Requirements
14
+
15
+ - Node.js (v18 or higher recommended)
16
+ - A Zentao instance accessible via API
17
+
18
+ ## Installation
19
+
20
+ 1. Clone the repository:
21
+
22
+ ```bash
23
+ git clone <repository-url>
24
+ cd zentao_mcp
25
+ ```
26
+
27
+ 2. Install dependencies:
28
+
29
+ ```bash
30
+ npm install
31
+ ```
32
+
33
+ 3. Configure environment variables:
34
+ Create a `.env` file in the root directory and add the following configurations:
35
+ ```env
36
+ ZENTAO_BASE_URL=https://your-zentao-url.com/api.php/v1
37
+ ZENTAO_ACCOUNT=your_username
38
+ ZENTAO_PASSWORD=your_password
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ This server communicates via standard input/output (`stdio`), making it compatible with any MCP client that supports stdio transport.
44
+
45
+ ### Integrating with MCP Clients (Claude Desktop, Cursor, etc.)
46
+
47
+ Once published to npm or GitHub, users can integrate this server into their MCP clients easily.
48
+
49
+ #### Option 1: Running via `npx` (Recommended if published to NPM)
50
+
51
+ Add the following to your MCP client configuration (e.g., `claude_desktop_config.json`):
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "zentao": {
57
+ "command": "npx",
58
+ "args": [
59
+ "-y",
60
+ "@dyno181cm.nexsoft/zentao_mcp"
61
+ ],
62
+ "env": {
63
+ "ZENTAO_BASE_URL": "https://your-zentao-url.com/api.php/v1",
64
+ "ZENTAO_ACCOUNT": "your_username",
65
+ "ZENTAO_PASSWORD": "your_password"
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ #### Option 2: Running from a Local Clone
73
+
74
+ If a user clones the repository locally, they can configure their client to run it directly:
75
+
76
+ ```json
77
+ {
78
+ "mcpServers": {
79
+ "zentao": {
80
+ "command": "node",
81
+ "args": ["/absolute/path/to/zentao_mcp/build/index.js"],
82
+ "env": {
83
+ "ZENTAO_BASE_URL": "https://your-zentao-url.com/api.php/v1",
84
+ "ZENTAO_ACCOUNT": "your_username",
85
+ "ZENTAO_PASSWORD": "your_password"
86
+ }
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### Available Tools
93
+
94
+ - **`zentao_get_execution_tasks`**
95
+ - **Inputs:**
96
+ - `executionId` (string | number) - Required. Execution ID.
97
+ - `page` (number) - Optional. Current page (default 1).
98
+ - `limit` (number) - Optional. Items per page (default 500).
99
+ - `moduleId` (string | number) - Optional. Filter by module ID.
100
+
101
+ - **`zentao_get_product_bugs`**
102
+ - **Inputs:**
103
+ - `productId` (string | number) - Required. Product ID.
104
+
105
+ - **`zentao_get_bug_details`**
106
+ - **Inputs:**
107
+ - `bugId` (string | number) - Required. Bug ID.
package/build/index.js ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerTools } from "./tools.js";
5
+ import * as dotenv from "dotenv";
6
+ dotenv.config();
7
+ const server = new McpServer({
8
+ name: "Zentao MCP Server",
9
+ version: "1.0.0"
10
+ });
11
+ registerTools(server);
12
+ async function main() {
13
+ const transport = new StdioServerTransport();
14
+ await server.connect(transport);
15
+ console.error("Zentao MCP Server started via stdio.");
16
+ }
17
+ main().catch(error => {
18
+ console.error("Fatal error starting server:", error);
19
+ process.exit(1);
20
+ });
@@ -0,0 +1,31 @@
1
+ import { ZentaoClient } from './zentaoClient.js';
2
+ import * as dotenv from 'dotenv';
3
+ dotenv.config();
4
+ async function main() {
5
+ try {
6
+ const client = new ZentaoClient();
7
+ console.log('Logging in to Zentao...');
8
+ await client.login();
9
+ console.log('Successfully logged in.');
10
+ console.log('\n--- Fetching execution tasks (Execution ID: 2) ---');
11
+ try {
12
+ const tasks = await client.getExecutionTasks(2, 1, 5);
13
+ console.log('Tasks result:', JSON.stringify(tasks, null, 2));
14
+ }
15
+ catch (e) {
16
+ console.error('Error fetching tasks:', e.response?.data || e.message);
17
+ }
18
+ console.log('\n--- Fetching bug details (Bug ID: 2) ---');
19
+ try {
20
+ const bug = await client.getBugDetails(2);
21
+ console.log('Bug details:', JSON.stringify(bug, null, 2));
22
+ }
23
+ catch (e) {
24
+ console.error('Error fetching bug:', e.response?.data || e.message);
25
+ }
26
+ }
27
+ catch (error) {
28
+ console.error('Fatal Error:', error.message || error);
29
+ }
30
+ }
31
+ main();
package/build/tools.js ADDED
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ import { ZentaoClient } from "./zentaoClient.js";
3
+ const client = new ZentaoClient();
4
+ export function registerTools(server) {
5
+ server.registerTool("zentao_get_execution_tasks", {
6
+ description: "Get a list of tasks in an execution (sprint), optionally filtered by moduleID",
7
+ inputSchema: {
8
+ executionId: z.union([z.string(), z.number()]).describe("Execution ID"),
9
+ page: z.number().optional().describe("Current page (default 1)"),
10
+ limit: z.number().optional().describe("Number of items per page (default 500)"),
11
+ moduleId: z.union([z.string(), z.number()]).optional().describe("Filter by module ID"),
12
+ }
13
+ }, async ({ executionId, page = 1, limit = 500, moduleId }) => {
14
+ const data = await client.getExecutionTasks(executionId, page, limit, moduleId);
15
+ return {
16
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
17
+ };
18
+ });
19
+ server.registerTool("zentao_get_product_bugs", {
20
+ description: "Get a list of bugs for a product",
21
+ inputSchema: {
22
+ productId: z.union([z.string(), z.number()]).describe("Product ID"),
23
+ }
24
+ }, async ({ productId }) => {
25
+ const data = await client.getProductBugs(productId);
26
+ return {
27
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
28
+ };
29
+ });
30
+ server.registerTool("zentao_get_bug_details", {
31
+ description: "Get detailed information of a bug",
32
+ inputSchema: {
33
+ bugId: z.union([z.string(), z.number()]).describe("Bug ID"),
34
+ }
35
+ }, async ({ bugId }) => {
36
+ const data = await client.getBugDetails(bugId);
37
+ return {
38
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
39
+ };
40
+ });
41
+ }
@@ -0,0 +1,101 @@
1
+ import axios from 'axios';
2
+ import * as dotenv from 'dotenv';
3
+ dotenv.config();
4
+ export class ZentaoClient {
5
+ client;
6
+ token = null;
7
+ account = process.env.ZENTAO_ACCOUNT || '';
8
+ password = process.env.ZENTAO_PASSWORD || '';
9
+ baseUrl = process.env.ZENTAO_BASE_URL || '';
10
+ constructor() {
11
+ this.client = axios.create({
12
+ baseURL: this.baseUrl,
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ },
16
+ });
17
+ // Add a request interceptor to automatically add the token and handle retries
18
+ this.client.interceptors.request.use(async (config) => {
19
+ // Don't add token if we are requesting a token
20
+ if (config.url === '/tokens')
21
+ return config;
22
+ if (!this.token) {
23
+ await this.login();
24
+ }
25
+ if (this.token) {
26
+ config.headers['Token'] = this.token;
27
+ }
28
+ return config;
29
+ });
30
+ // Add response interceptor to handle 401 Unauthorized (token expiry)
31
+ this.client.interceptors.response.use((response) => response, async (error) => {
32
+ const originalRequest = error.config;
33
+ if (error.response?.status === 401 && !originalRequest._retry && originalRequest.url !== '/tokens') {
34
+ originalRequest._retry = true;
35
+ await this.login();
36
+ if (this.token) {
37
+ originalRequest.headers['Token'] = this.token;
38
+ return this.client(originalRequest);
39
+ }
40
+ }
41
+ return Promise.reject(error);
42
+ });
43
+ }
44
+ async login() {
45
+ try {
46
+ if (!this.account || !this.password) {
47
+ throw new Error('ZENTAO_ACCOUNT or ZENTAO_PASSWORD is not set in environment variables');
48
+ }
49
+ const response = await this.client.post('/tokens', {
50
+ account: this.account,
51
+ password: this.password,
52
+ });
53
+ if (response.data && response.data.token) {
54
+ this.token = response.data.token;
55
+ }
56
+ else {
57
+ throw new Error('Login failed: Token not found in response');
58
+ }
59
+ }
60
+ catch (error) {
61
+ console.error('Failed to login to Zentao:', error.message);
62
+ throw error;
63
+ }
64
+ }
65
+ async getUsers(page = 1, limit = 100) {
66
+ const res = await this.client.get(`/users?page=${page}&limit=${limit}`);
67
+ return res.data;
68
+ }
69
+ async getProjects() {
70
+ const res = await this.client.get('/projects');
71
+ return res.data;
72
+ }
73
+ async getProjectExecutions(projectId) {
74
+ const res = await this.client.get(`/projects/${projectId}/executions`);
75
+ return res.data;
76
+ }
77
+ async getExecutionDetails(executionId) {
78
+ const res = await this.client.get(`/executions/${executionId}`);
79
+ return res.data;
80
+ }
81
+ async getExecutionTasks(executionId, page = 1, limit = 500, moduleId) {
82
+ let url = `/executions/${executionId}/tasks?page=${page}&limit=${limit}`;
83
+ if (moduleId) {
84
+ url += `&moduleID=${moduleId}`;
85
+ }
86
+ const res = await this.client.get(url);
87
+ return res.data;
88
+ }
89
+ async getTaskModules(executionId) {
90
+ const res = await this.client.get(`/modules?type=task&id=${executionId}`);
91
+ return res.data;
92
+ }
93
+ async getProductBugs(productId) {
94
+ const res = await this.client.get(`/products/${productId}/bugs`);
95
+ return res.data;
96
+ }
97
+ async getBugDetails(bugId) {
98
+ const res = await this.client.get(`/bugs/${bugId}`);
99
+ return res.data;
100
+ }
101
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@dyno181cm.nexsoft/zentao_mcp",
3
+ "version": "1.0.0",
4
+ "description": "Zentao MCP Server",
5
+ "main": "build/index.js",
6
+ "bin": {
7
+ "zentao_mcp": "build/index.js"
8
+ },
9
+ "type": "module",
10
+ "files": [
11
+ "build"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node build/index.js",
16
+ "dev": "tsc --watch",
17
+ "test:api": "tsx tests/testApi.ts"
18
+ },
19
+ "dependencies": {
20
+ "@modelcontextprotocol/sdk": "^1.1.0",
21
+ "axios": "^1.7.9",
22
+ "dotenv": "^16.4.7"
23
+ },
24
+ "devDependencies": {
25
+ "@types/jest": "^30.0.0",
26
+ "@types/node": "^22.10.2",
27
+ "jest": "^30.4.2",
28
+ "ts-jest": "^29.4.11",
29
+ "tsx": "^4.23.0",
30
+ "typescript": "^5.7.2"
31
+ }
32
+ }