@leeguoo/zentao-mcp 0.4.1 → 0.5.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
@@ -1,177 +1,107 @@
1
1
  # zentao-mcp
2
2
 
3
- MCP server for ZenTao RESTful APIs (products + bugs).
4
-
5
- ## Quick Start
6
-
7
- ### Cursor IDE
8
-
9
- 1. Open Cursor Settings (⌘, on Mac or Ctrl+, on Windows/Linux)
10
- 2. Navigate to **Features** → **Model Context Protocol**
11
- 3. Click **Edit Config** to open `~/.cursor/mcp.json` (or create it)
12
- 4. Add the following configuration:
13
-
14
- ```json
15
- {
16
- "mcpServers": {
17
- "zentao-mcp": {
18
- "command": "npx",
19
- "args": [
20
- "-y",
21
- "@leeguoo/zentao-mcp",
22
- "--zentao-url=https://zentao.example.com/zentao",
23
- "--zentao-account=leo",
24
- "--zentao-password=***",
25
- "--stdio"
26
- ]
27
- }
28
- }
29
- }
30
- ```
3
+ ZenTao CLI for products + bugs.
31
4
 
32
- 5. Restart Cursor IDE
5
+ ## Installation
33
6
 
34
- ### Other MCP Clients (Claude Desktop, etc.)
7
+ Global install:
35
8
 
36
- For clients using TOML configuration (e.g., Claude Desktop), add to your MCP config file:
9
+ ```bash
10
+ npm i -g @leeguoo/zentao-mcp
11
+ ```
37
12
 
38
- ```toml
39
- [mcp_servers."zentao-mcp"]
40
- command = "npx"
41
- args = [
42
- "-y",
43
- "@leeguoo/zentao-mcp",
44
- "--zentao-url=https://zentao.example.com/zentao",
45
- "--zentao-account=leo",
46
- "--zentao-password=***",
47
- "--stdio"
48
- ]
13
+ Or use without installing:
14
+
15
+ ```bash
16
+ npx -y @leeguoo/zentao-mcp --help
49
17
  ```
50
18
 
51
- **Config file locations:**
52
- - Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.toml` (Mac) or `%APPDATA%\Claude\claude_desktop_config.toml` (Windows)
53
- - Cursor: `~/.cursor/mcp.json` (JSON format)
19
+ This installs the `zentao` command (and keeps `zentao-mcp` as a compatibility alias).
54
20
 
55
21
  ## Configuration
56
22
 
57
23
  ### Required Parameters
58
24
 
59
- You can configure the server using CLI arguments or environment variables:
25
+ You can configure the CLI using CLI arguments or environment variables:
26
+
27
+ CLI arguments:
60
28
 
61
- **CLI Arguments:**
62
29
  - `--zentao-url` (e.g. `https://zentao.example.com/zentao`)
63
30
  - `--zentao-account`
64
31
  - `--zentao-password`
65
32
 
66
- **Environment Variables:**
67
- - `ZENTAO_URL` (e.g. `https://zentao.example.com/zentao`)
33
+ Environment variables:
34
+
35
+ - `ZENTAO_URL`
68
36
  - `ZENTAO_ACCOUNT`
69
37
  - `ZENTAO_PASSWORD`
70
38
 
71
- ### Using Environment Variables in Cursor
72
-
73
- If you prefer to use environment variables instead of CLI args, you can configure them in Cursor:
74
-
75
- ```json
76
- {
77
- "mcpServers": {
78
- "zentao-mcp": {
79
- "command": "npx",
80
- "args": ["-y", "@leeguoo/zentao-mcp", "--stdio"],
81
- "env": {
82
- "ZENTAO_URL": "https://zentao.example.com/zentao",
83
- "ZENTAO_ACCOUNT": "leo",
84
- "ZENTAO_PASSWORD": "***"
85
- }
86
- }
87
- }
88
- }
39
+ Tip: `ZENTAO_URL` should include the ZenTao base path (often `/zentao`).
40
+
41
+ ## Commands
42
+
43
+ List products:
44
+
45
+ ```bash
46
+ zentao products list
89
47
  ```
90
48
 
91
- **Tip:** `ZENTAO_URL` should include the ZenTao base path (often `/zentao`).
92
-
93
- ## Tools
94
-
95
- The MCP server provides four tools that can be triggered by natural language in Cursor:
96
-
97
- - **`zentao_products_list`** - List all products
98
- - **`zentao_bugs_list`** - List bugs for a specific product
99
- - **`zentao_bug_get`** - Get bug details by ID
100
- - **`zentao_bugs_mine`** - List my bugs by assignment or creator (status filter supported)
101
-
102
- ### Usage Examples
103
-
104
- After configuring the MCP server in Cursor, you can use natural language to interact with ZenTao:
105
-
106
- **English:**
107
- - "Show me all products"
108
- - "List bugs for product 1"
109
- - "Show bug 123"
110
- - "Show me bugs"
111
- - "Show my bugs"
112
- - "List bugs assigned to me"
113
- - "View bugs in product 2"
114
-
115
- **Chinese (中文):**
116
- - "看bug" / "查看bug" / "显示bug"
117
- - "产品1的bug列表"
118
- - "查看bug 123"
119
- - "显示所有产品"
120
- - "查看产品2的问题"
121
- - "我的bug"
122
- - "分配给我的bug"
123
-
124
- The AI will automatically:
125
- 1. Use `zentao_products_list` to get product IDs when needed
126
- 2. Use `zentao_bugs_list` when you ask to see bugs
127
- 3. Use `zentao_bug_get` when you ask for bug details
128
- 4. Use `zentao_bugs_mine` when you ask for your own bugs
129
-
130
- ### Tool Parameters
131
-
132
- **zentao_products_list:**
133
- ```json
134
- {
135
- "page": 1,
136
- "limit": 1000
137
- }
49
+ List bugs for a product:
50
+
51
+ ```bash
52
+ zentao bugs list --product 1
138
53
  ```
139
54
 
140
- **zentao_bugs_list:**
141
- ```json
142
- {
143
- "product": 1,
144
- "page": 1,
145
- "limit": 20
146
- }
55
+ Get bug details:
56
+
57
+ ```bash
58
+ zentao bug get --id 123
147
59
  ```
148
60
 
149
- **zentao_bug_get:**
150
- ```json
151
- {
152
- "id": 123
153
- }
61
+ List my bugs:
62
+
63
+ ```bash
64
+ zentao bugs mine --scope assigned --status active
154
65
  ```
155
66
 
156
- **zentao_bugs_mine:**
157
- ```json
158
- {
159
- "status": "active",
160
- "scope": "assigned",
161
- "includeZero": false,
162
- "includeDetails": true,
163
- "maxItems": 50
164
- }
67
+ Self test:
68
+
69
+ ```bash
70
+ zentao self-test
71
+ ```
72
+
73
+ ## Login
74
+
75
+ Save credentials locally (stored as plaintext TOML under your user config directory):
76
+
77
+ ```bash
78
+ zentao login --zentao-url=https://zentao.example.com/zentao --zentao-account=leo --zentao-password=***
79
+ ```
80
+
81
+ Config file:
82
+
83
+ - `~/.config/zentao/config.toml` (or `$XDG_CONFIG_HOME/zentao/config.toml`)
84
+
85
+ Then commands can omit auth flags:
86
+
87
+ ```bash
88
+ zentao whoami
89
+ zentao products list
90
+ ```
91
+
92
+ ## Release (maintainers)
93
+
94
+ Requires `git`, `npm`, and `gh`.
95
+
96
+ ```bash
97
+ zentao release patch --dry-run
165
98
  ```
166
99
 
167
100
  ## Local Development
168
101
 
169
102
  ```bash
170
103
  pnpm install
171
- ZENTAO_URL=https://zentao.example.com/zentao \\
172
- ZENTAO_ACCOUNT=leo \\
173
- ZENTAO_PASSWORD=*** \\
174
- pnpm start
104
+ pnpm test
175
105
  ```
176
106
 
177
107
  ## Security
package/package.json CHANGED
@@ -1,14 +1,10 @@
1
1
  {
2
2
  "name": "@leeguoo/zentao-mcp",
3
- "version": "0.4.1",
4
- "description": "MCP server for ZenTao RESTful APIs",
3
+ "version": "0.5.0",
4
+ "description": "ZenTao CLI for products + bugs",
5
5
  "keywords": [
6
6
  "zentao",
7
7
  "chandao",
8
- "mcp",
9
- "modelcontextprotocol",
10
- "llm",
11
- "ai",
12
8
  "api",
13
9
  "rest",
14
10
  "bug-tracker",
@@ -20,10 +16,12 @@
20
16
  ],
21
17
  "type": "module",
22
18
  "bin": {
19
+ "zentao": "src/index.js",
23
20
  "zentao-mcp": "src/index.js"
24
21
  },
25
22
  "files": [
26
23
  "src",
24
+ "skills",
27
25
  "README.md"
28
26
  ],
29
27
  "publishConfig": {
@@ -31,13 +29,15 @@
31
29
  },
32
30
  "scripts": {
33
31
  "start": "node src/index.js",
34
- "self-test": "node scripts/self-test.mjs",
35
- "release": "./scripts/release.sh",
36
- "release:patch": "./scripts/release.sh patch",
37
- "release:minor": "./scripts/release.sh minor",
38
- "release:major": "./scripts/release.sh major"
32
+ "self-test": "node src/index.js self-test",
33
+ "release": "node src/index.js release",
34
+ "release:patch": "node src/index.js release patch",
35
+ "release:minor": "node src/index.js release minor",
36
+ "release:major": "node src/index.js release major",
37
+ "test": "node --test"
39
38
  },
40
- "dependencies": {
41
- "@modelcontextprotocol/sdk": "^1.25.1"
39
+ "dependencies": {},
40
+ "engines": {
41
+ "node": ">=18"
42
42
  }
43
43
  }
@@ -0,0 +1,106 @@
1
+ # zentao (ZenTao CLI)
2
+
3
+ This package provides a CLI for ZenTao REST API (products + bugs).
4
+
5
+ ## Installation
6
+
7
+ Global install:
8
+
9
+ ```bash
10
+ npm i -g @leeguoo/zentao-mcp
11
+ ```
12
+
13
+ Or use without installing:
14
+
15
+ ```bash
16
+ npx -y @leeguoo/zentao-mcp --help
17
+ ```
18
+
19
+ This installs the `zentao` command (and keeps `zentao-mcp` as a compatibility alias).
20
+
21
+ ## Authentication
22
+
23
+ You can pass credentials via environment variables:
24
+
25
+ ```bash
26
+ export ZENTAO_URL="https://zentao.example.com/zentao"
27
+ export ZENTAO_ACCOUNT="leo"
28
+ export ZENTAO_PASSWORD="***"
29
+ ```
30
+
31
+ Or via CLI flags:
32
+
33
+ - `--zentao-url`
34
+ - `--zentao-account`
35
+ - `--zentao-password`
36
+
37
+ ## Commands
38
+
39
+ ### List products
40
+
41
+ ```bash
42
+ zentao products list
43
+ ```
44
+
45
+ ### List bugs for a product
46
+
47
+ ```bash
48
+ zentao bugs list --product 1
49
+ ```
50
+
51
+ ### Get bug details
52
+
53
+ ```bash
54
+ zentao bug get --id 123
55
+ ```
56
+
57
+ ### List my bugs
58
+
59
+ ```bash
60
+ zentao bugs mine --scope assigned --status active
61
+ ```
62
+
63
+ Common options:
64
+
65
+ - `--scope`: `assigned|opened|resolved|all`
66
+ - `--status`: `active|resolved|closed|all` (supports `,` or `|` separated)
67
+ - `--include-details`: include bug list in response
68
+ - `--product-ids`: limit scan to specific products, e.g. `--product-ids=1,2,3`
69
+
70
+ ### Self test
71
+
72
+ ```bash
73
+ zentao self-test
74
+ ```
75
+
76
+ Use `--expected N` to make it CI-friendly (exit code 2 when mismatch):
77
+
78
+ ```bash
79
+ zentao self-test --expected 0
80
+ ```
81
+
82
+ ## Login / Whoami
83
+
84
+ Save credentials locally:
85
+
86
+ ```bash
87
+ zentao login --zentao-url=https://zentao.example.com/zentao --zentao-account=leo --zentao-password=***
88
+ ```
89
+
90
+ Config file:
91
+
92
+ - `~/.config/zentao/config.toml` (or `$XDG_CONFIG_HOME/zentao/config.toml`)
93
+
94
+ Then:
95
+
96
+ ```bash
97
+ zentao whoami
98
+ ```
99
+
100
+ ### Release (maintainers)
101
+
102
+ Requires `git`, `npm`, and `gh`.
103
+
104
+ ```bash
105
+ zentao release patch --dry-run
106
+ ```
@@ -0,0 +1,99 @@
1
+ export function parseCliArgs(argv) {
2
+ const args = {};
3
+ for (let i = 0; i < argv.length; i += 1) {
4
+ const raw = argv[i];
5
+ if (!raw.startsWith("--")) continue;
6
+ const [flag, inlineValue] = raw.split("=", 2);
7
+ const key = flag.replace(/^--/, "");
8
+ if (inlineValue !== undefined) {
9
+ args[key] = inlineValue;
10
+ continue;
11
+ }
12
+ const next = argv[i + 1];
13
+ if (next && !next.startsWith("--")) {
14
+ args[key] = next;
15
+ i += 1;
16
+ continue;
17
+ }
18
+ args[key] = true;
19
+ }
20
+ return args;
21
+ }
22
+
23
+ export function hasHelpFlag(argv) {
24
+ return argv.includes("--help") || argv.includes("-h");
25
+ }
26
+
27
+ export function getOption(cliArgs, env, envName, cliName) {
28
+ if (cliArgs[cliName]) return cliArgs[cliName];
29
+ const envValue = env[envName];
30
+ if (envValue) return envValue;
31
+ return null;
32
+ }
33
+
34
+ function isFlagToken(raw) {
35
+ return raw.startsWith("-") && raw !== "-";
36
+ }
37
+
38
+ export function splitSubcommand(argv) {
39
+ // Goal: find the first positional argument that isn't a flag or a flag value.
40
+ // This allows: `zentao-mcp self-test --foo=bar`.
41
+ // It also avoids treating `--zentao-url https://...` value as a subcommand.
42
+ for (let i = 0; i < argv.length; i += 1) {
43
+ const raw = argv[i];
44
+
45
+ if (raw === "--") {
46
+ return { command: null, commandArgv: argv };
47
+ }
48
+
49
+ if (raw.startsWith("--")) {
50
+ const hasInlineValue = raw.includes("=");
51
+ if (hasInlineValue) continue;
52
+ const next = argv[i + 1];
53
+ if (next && !isFlagToken(next)) {
54
+ i += 1;
55
+ }
56
+ continue;
57
+ }
58
+
59
+ if (raw === "-h") continue;
60
+ if (raw === "-v") continue;
61
+ if (raw.startsWith("-")) continue;
62
+
63
+ return { command: raw, commandArgv: argv.slice(i + 1) };
64
+ }
65
+
66
+ return { command: null, commandArgv: argv };
67
+ }
68
+
69
+ function findFirstPositional(argv) {
70
+ for (let i = 0; i < argv.length; i += 1) {
71
+ const raw = argv[i];
72
+ if (raw === "--") continue;
73
+
74
+ if (raw.startsWith("--")) {
75
+ const hasInlineValue = raw.includes("=");
76
+ if (hasInlineValue) continue;
77
+ const next = argv[i + 1];
78
+ if (next && !isFlagToken(next)) {
79
+ i += 1;
80
+ }
81
+ continue;
82
+ }
83
+
84
+ if (raw === "-h") continue;
85
+ if (raw === "-v") continue;
86
+ if (raw.startsWith("-")) continue;
87
+
88
+ return { index: i, value: raw };
89
+ }
90
+
91
+ return { index: -1, value: null };
92
+ }
93
+
94
+ export function extractCommand(argv) {
95
+ const { index, value } = findFirstPositional(argv);
96
+ if (!value) return { command: null, argv };
97
+ const nextArgv = argv.slice(0, index).concat(argv.slice(index + 1));
98
+ return { command: value, argv: nextArgv };
99
+ }
@@ -0,0 +1,49 @@
1
+ export function printRootHelp() {
2
+ // Keep this plain text: many users run via npx and paste output.
3
+ process.stdout.write(`zentao - ZenTao CLI\n\n`);
4
+ process.stdout.write(`Usage:\n`);
5
+ process.stdout.write(` zentao login [--zentao-url ... --zentao-account ... --zentao-password ...] [--yes]\n`);
6
+ process.stdout.write(` zentao whoami\n`);
7
+ process.stdout.write(` zentao products list [--page N] [--limit N]\n`);
8
+ process.stdout.write(` zentao bugs list --product <id> [--page N] [--limit N]\n`);
9
+ process.stdout.write(` zentao bug get --id <bugId>\n`);
10
+ process.stdout.write(` zentao bugs mine [--scope ...] [--status ...] [--include-details]\n`);
11
+ process.stdout.write(` zentao self-test [--expected N]\n`);
12
+ process.stdout.write(` zentao release [patch|minor|major] [--dry-run] [--yes]\n\n`);
13
+ process.stdout.write(`Auth options:\n`);
14
+ process.stdout.write(` --zentao-url or env ZENTAO_URL\n`);
15
+ process.stdout.write(` --zentao-account or env ZENTAO_ACCOUNT\n`);
16
+ process.stdout.write(` --zentao-password or env ZENTAO_PASSWORD\n`);
17
+ process.stdout.write(` --help, -h show help\n\n`);
18
+ process.stdout.write(`Subcommands:\n`);
19
+ process.stdout.write(` login save credentials locally\n`);
20
+ process.stdout.write(` whoami show current account\n`);
21
+ process.stdout.write(` products ZenTao products\n`);
22
+ process.stdout.write(` bugs ZenTao bugs\n`);
23
+ process.stdout.write(` bug ZenTao bug\n`);
24
+ process.stdout.write(` self-test run a basic API roundtrip\n`);
25
+ process.stdout.write(` release version bump + tag + gh release + npm publish\n`);
26
+ }
27
+
28
+ export function printSelfTestHelp() {
29
+ process.stdout.write(`zentao self-test - verify API access\n\n`);
30
+ process.stdout.write(`Usage:\n`);
31
+ process.stdout.write(` zentao self-test --zentao-url=... --zentao-account=... --zentao-password=...\n`);
32
+ process.stdout.write(` ZENTAO_URL=... ZENTAO_ACCOUNT=... ZENTAO_PASSWORD=... zentao self-test\n\n`);
33
+ process.stdout.write(`Options:\n`);
34
+ process.stdout.write(` --expected N exit 2 if total != N\n`);
35
+ process.stdout.write(` --help, -h show help\n`);
36
+ }
37
+
38
+ export function printReleaseHelp() {
39
+ process.stdout.write(`zentao release - create a new release\n\n`);
40
+ process.stdout.write(`Usage:\n`);
41
+ process.stdout.write(` zentao release [patch|minor|major] [--dry-run] [--yes]\n\n`);
42
+ process.stdout.write(`Options:\n`);
43
+ process.stdout.write(` --dry-run print planned commands, do not run\n`);
44
+ process.stdout.write(` --yes auto-confirm prompts (dirty git, npm login)\n`);
45
+ process.stdout.write(` --skip-push do not push commits/tags\n`);
46
+ process.stdout.write(` --skip-github-release do not create GitHub release\n`);
47
+ process.stdout.write(` --skip-publish do not npm publish\n`);
48
+ process.stdout.write(` --help, -h show help\n`);
49
+ }
@@ -0,0 +1,27 @@
1
+ import process from "node:process";
2
+ import { extractCommand, hasHelpFlag, parseCliArgs } from "../cli/args.js";
3
+ import { createClientFromCli } from "../zentao/client.js";
4
+
5
+ function printHelp() {
6
+ process.stdout.write(`zentao-mcp bug get\n\n`);
7
+ process.stdout.write(`Usage:\n`);
8
+ process.stdout.write(` zentao-mcp bug get --id <bugId>\n`);
9
+ }
10
+
11
+ export async function runBug({ argv = [], env = process.env } = {}) {
12
+ if (hasHelpFlag(argv)) {
13
+ printHelp();
14
+ return;
15
+ }
16
+
17
+ const { command: sub, argv: argvWithoutSub } = extractCommand(argv);
18
+ if (sub !== "get") throw new Error(`Unknown bug subcommand: ${sub || "(missing)"}`);
19
+
20
+ const cliArgs = parseCliArgs(argvWithoutSub);
21
+ const id = cliArgs.id;
22
+ if (!id) throw new Error("Missing --id");
23
+
24
+ const api = createClientFromCli({ argv: argvWithoutSub, env });
25
+ const result = await api.getBug({ id });
26
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
27
+ }
@@ -0,0 +1,68 @@
1
+ import process from "node:process";
2
+ import { extractCommand, hasHelpFlag, parseCliArgs } from "../cli/args.js";
3
+ import { createClientFromCli } from "../zentao/client.js";
4
+
5
+ function parseCsvIntegers(value) {
6
+ if (value === undefined || value === null || value === "") return null;
7
+ if (Array.isArray(value)) {
8
+ const nested = value.flatMap((item) => String(item).split(/[,|]/));
9
+ const parsed = nested.map((item) => Number(item)).filter((n) => Number.isFinite(n));
10
+ return parsed.length ? parsed : null;
11
+ }
12
+ const tokens = String(value)
13
+ .split(/[,|]/)
14
+ .map((item) => item.trim())
15
+ .filter(Boolean);
16
+ const parsed = tokens.map((item) => Number(item)).filter((n) => Number.isFinite(n));
17
+ return parsed.length ? parsed : null;
18
+ }
19
+
20
+ function printHelp() {
21
+ process.stdout.write(`zentao-mcp bugs <subcommand>\n\n`);
22
+ process.stdout.write(`Usage:\n`);
23
+ process.stdout.write(` zentao-mcp bugs list --product <id> [--page N] [--limit N]\n`);
24
+ process.stdout.write(` zentao-mcp bugs mine [--scope assigned|opened|resolved|all] [--status active|resolved|closed|all] [--include-details]\n`);
25
+ }
26
+
27
+ export async function runBugs({ argv = [], env = process.env } = {}) {
28
+ if (hasHelpFlag(argv)) {
29
+ printHelp();
30
+ return;
31
+ }
32
+
33
+ const { command: sub, argv: argvWithoutSub } = extractCommand(argv);
34
+ const cliArgs = parseCliArgs(argvWithoutSub);
35
+ const api = createClientFromCli({ argv: argvWithoutSub, env });
36
+
37
+ if (sub === "list") {
38
+ const product = cliArgs.product;
39
+ if (!product) throw new Error("Missing --product");
40
+ const result = await api.listBugs({
41
+ product,
42
+ page: cliArgs.page,
43
+ limit: cliArgs.limit,
44
+ });
45
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
46
+ return;
47
+ }
48
+
49
+ if (sub === "mine") {
50
+ const includeDetails = Boolean(cliArgs["include-details"]);
51
+ const includeZero = Boolean(cliArgs["include-zero"]);
52
+ const productIds = parseCsvIntegers(cliArgs["product-ids"]);
53
+ const result = await api.bugsMine({
54
+ account: cliArgs.account,
55
+ scope: cliArgs.scope,
56
+ status: cliArgs.status,
57
+ productIds,
58
+ includeZero,
59
+ perPage: cliArgs["per-page"],
60
+ maxItems: cliArgs["max-items"],
61
+ includeDetails,
62
+ });
63
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
64
+ return;
65
+ }
66
+
67
+ throw new Error(`Unknown bugs subcommand: ${sub || "(missing)"}`);
68
+ }